##// END OF EJS Templates
Merge pull request #3011 from minrk/kernelclient...
Brian E. Granger -
r10354:ce1ad539 merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,52
1 """ Defines a KernelClient that provides signals and slots.
2 """
3
4 from IPython.external.qt import QtCore
5
6 # Local imports
7 from IPython.utils.traitlets import Bool, Instance
8
9 from IPython.kernel import KernelManager
10 from IPython.kernel.restarter import KernelRestarter
11
12 from .kernel_mixins import QtKernelManagerMixin, QtKernelRestarterMixin
13
14
15 class QtKernelRestarter(KernelRestarter, QtKernelRestarterMixin):
16
17 def start(self):
18 if self._timer is None:
19 self._timer = QtCore.QTimer()
20 self._timer.timeout.connect(self.poll)
21 self._timer.start(self.time_to_dead * 1000)
22
23 def stop(self):
24 self._timer.stop()
25
26 def poll(self):
27 super(QtKernelRestarter, self).poll()
28
29
30 class QtKernelManager(KernelManager, QtKernelManagerMixin):
31 """A KernelManager with Qt signals for restart"""
32
33 autorestart = Bool(True, config=True)
34
35 def start_restarter(self):
36 if self.autorestart and self.has_kernel:
37 if self._restarter is None:
38 self._restarter = QtKernelRestarter(
39 kernel_manager=self,
40 config=self.config,
41 log=self.log,
42 )
43 self._restarter.add_callback(self._handle_kernel_restarted)
44 self._restarter.start()
45
46 def stop_restarter(self):
47 if self.autorestart:
48 if self._restarter is not None:
49 self._restarter.stop()
50
51 def _handle_kernel_restarted(self):
52 self.kernel_restarted.emit()
@@ -0,0 +1,1
1 from .client import BlockingKernelClient No newline at end of file
@@ -0,0 +1,33
1 """Implements a fully blocking kernel client.
2
3 Useful for test suites and blocking terminal interfaces.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2013 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15
16 from IPython.utils.traitlets import Type
17 from IPython.kernel.client import KernelClient
18 from .channels import (
19 BlockingIOPubChannel, BlockingHBChannel,
20 BlockingShellChannel, BlockingStdInChannel
21 )
22
23 #-----------------------------------------------------------------------------
24 # Blocking kernel manager
25 #-----------------------------------------------------------------------------
26
27 class BlockingKernelClient(KernelClient):
28
29 # The classes to use for the various channels
30 shell_channel_class = Type(BlockingShellChannel)
31 iopub_channel_class = Type(BlockingIOPubChannel)
32 stdin_channel_class = Type(BlockingStdInChannel)
33 hb_channel_class = Type(BlockingHBChannel)
@@ -0,0 +1,126
1 """Abstract base classes for kernel client channels"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # Standard library imports
15 import abc
16
17 #-----------------------------------------------------------------------------
18 # Channels
19 #-----------------------------------------------------------------------------
20
21
22 class ChannelABC(object):
23 """A base class for all channel ABCs."""
24
25 __metaclass__ = abc.ABCMeta
26
27 @abc.abstractmethod
28 def start(self):
29 pass
30
31 @abc.abstractmethod
32 def stop(self):
33 pass
34
35 @abc.abstractmethod
36 def is_alive(self):
37 pass
38
39
40 class ShellChannelABC(ChannelABC):
41 """ShellChannel ABC.
42
43 The docstrings for this class can be found in the base implementation:
44
45 `IPython.kernel.channels.ShellChannel`
46 """
47
48 @abc.abstractproperty
49 def allow_stdin(self):
50 pass
51
52 @abc.abstractmethod
53 def execute(self, code, silent=False, store_history=True,
54 user_variables=None, user_expressions=None, allow_stdin=None):
55 pass
56
57 @abc.abstractmethod
58 def complete(self, text, line, cursor_pos, block=None):
59 pass
60
61 @abc.abstractmethod
62 def object_info(self, oname, detail_level=0):
63 pass
64
65 @abc.abstractmethod
66 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
67 pass
68
69 @abc.abstractmethod
70 def kernel_info(self):
71 pass
72
73 @abc.abstractmethod
74 def shutdown(self, restart=False):
75 pass
76
77
78 class IOPubChannelABC(ChannelABC):
79 """IOPubChannel ABC.
80
81 The docstrings for this class can be found in the base implementation:
82
83 `IPython.kernel.channels.IOPubChannel`
84 """
85
86 @abc.abstractmethod
87 def flush(self, timeout=1.0):
88 pass
89
90
91 class StdInChannelABC(ChannelABC):
92 """StdInChannel ABC.
93
94 The docstrings for this class can be found in the base implementation:
95
96 `IPython.kernel.channels.StdInChannel`
97 """
98
99 @abc.abstractmethod
100 def input(self, string):
101 pass
102
103
104 class HBChannelABC(ChannelABC):
105 """HBChannel ABC.
106
107 The docstrings for this class can be found in the base implementation:
108
109 `IPython.kernel.channels.HBChannel`
110 """
111
112 @abc.abstractproperty
113 def time_to_dead(self):
114 pass
115
116 @abc.abstractmethod
117 def pause(self):
118 pass
119
120 @abc.abstractmethod
121 def unpause(self):
122 pass
123
124 @abc.abstractmethod
125 def is_beating(self):
126 pass
@@ -0,0 +1,198
1 """Base class to manage the interaction with a running kernel
2 """
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from __future__ import absolute_import
16
17 import zmq
18
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
22 Any, Instance, Type,
23 )
24
25 from .zmq.session import Session
26 from .channels import (
27 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
29 )
30 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
32
33
34 #-----------------------------------------------------------------------------
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
40
41 There are four channels associated with each kernel:
42
43 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
47
48 The methods of the channels are exposed as methods of the client itself
49 (KernelClient.execute, complete, history, etc.).
50 See the channels themselves for documentation of these methods.
51
52 """
53
54 # The PyZMQ Context to use for communication with the kernel.
55 context = Instance(zmq.Context)
56 def _context_default(self):
57 return zmq.Context.instance()
58
59 # The Session to use for communication with the kernel.
60 session = Instance(Session)
61 def _session_default(self):
62 return Session(config=self.config)
63
64 # The classes to use for the various channels
65 shell_channel_class = Type(ShellChannel)
66 iopub_channel_class = Type(IOPubChannel)
67 stdin_channel_class = Type(StdInChannel)
68 hb_channel_class = Type(HBChannel)
69
70 # Protected traits
71 _shell_channel = Any
72 _iopub_channel = Any
73 _stdin_channel = Any
74 _hb_channel = Any
75
76 #--------------------------------------------------------------------------
77 # Channel proxy methods
78 #--------------------------------------------------------------------------
79
80 def _get_msg(channel, *args, **kwargs):
81 return channel.get_msg(*args, **kwargs)
82
83 def get_shell_msg(self, *args, **kwargs):
84 """Get a message from the shell channel"""
85 return self.shell_channel.get_msg(*args, **kwargs)
86
87 def get_iopub_msg(self, *args, **kwargs):
88 """Get a message from the iopub channel"""
89 return self.iopub_channel.get_msg(*args, **kwargs)
90
91 def get_stdin_msg(self, *args, **kwargs):
92 """Get a message from the stdin channel"""
93 return self.stdin_channel.get_msg(*args, **kwargs)
94
95 #--------------------------------------------------------------------------
96 # Channel management methods
97 #--------------------------------------------------------------------------
98
99 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
100 """Starts the channels for this kernel.
101
102 This will create the channels if they do not exist and then start
103 them (their activity runs in a thread). If port numbers of 0 are
104 being used (random ports) then you must first call
105 :method:`start_kernel`. If the channels have been stopped and you
106 call this, :class:`RuntimeError` will be raised.
107 """
108 if shell:
109 self.shell_channel.start()
110 for method in self.shell_channel.proxy_methods:
111 setattr(self, method, getattr(self.shell_channel, method))
112 if iopub:
113 self.iopub_channel.start()
114 for method in self.iopub_channel.proxy_methods:
115 setattr(self, method, getattr(self.iopub_channel, method))
116 if stdin:
117 self.stdin_channel.start()
118 for method in self.stdin_channel.proxy_methods:
119 setattr(self, method, getattr(self.stdin_channel, method))
120 self.shell_channel.allow_stdin = True
121 else:
122 self.shell_channel.allow_stdin = False
123 if hb:
124 self.hb_channel.start()
125
126 def stop_channels(self):
127 """Stops all the running channels for this kernel.
128
129 This stops their event loops and joins their threads.
130 """
131 if self.shell_channel.is_alive():
132 self.shell_channel.stop()
133 if self.iopub_channel.is_alive():
134 self.iopub_channel.stop()
135 if self.stdin_channel.is_alive():
136 self.stdin_channel.stop()
137 if self.hb_channel.is_alive():
138 self.hb_channel.stop()
139
140 @property
141 def channels_running(self):
142 """Are any of the channels created and running?"""
143 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
144 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
145
146 @property
147 def shell_channel(self):
148 """Get the shell channel object for this kernel."""
149 if self._shell_channel is None:
150 self._shell_channel = self.shell_channel_class(
151 self.context, self.session, self._make_url('shell')
152 )
153 return self._shell_channel
154
155 @property
156 def iopub_channel(self):
157 """Get the iopub channel object for this kernel."""
158 if self._iopub_channel is None:
159 self._iopub_channel = self.iopub_channel_class(
160 self.context, self.session, self._make_url('iopub')
161 )
162 return self._iopub_channel
163
164 @property
165 def stdin_channel(self):
166 """Get the stdin channel object for this kernel."""
167 if self._stdin_channel is None:
168 self._stdin_channel = self.stdin_channel_class(
169 self.context, self.session, self._make_url('stdin')
170 )
171 return self._stdin_channel
172
173 @property
174 def hb_channel(self):
175 """Get the hb channel object for this kernel."""
176 if self._hb_channel is None:
177 self._hb_channel = self.hb_channel_class(
178 self.context, self.session, self._make_url('hb')
179 )
180 return self._hb_channel
181
182 def is_alive(self):
183 """Is the kernel process still running?"""
184 if self._hb_channel is not None:
185 # We didn't start the kernel with this KernelManager so we
186 # use the heartbeat.
187 return self._hb_channel.is_beating()
188 else:
189 # no heartbeat and not local, we can't tell if it's running,
190 # so naively return True
191 return True
192
193
194 #-----------------------------------------------------------------------------
195 # ABC Registration
196 #-----------------------------------------------------------------------------
197
198 KernelClientABC.register(KernelClient)
@@ -0,0 +1,81
1 """Abstract base class for kernel clients"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # Standard library imports
15 import abc
16
17 #-----------------------------------------------------------------------------
18 # Main kernel client class
19 #-----------------------------------------------------------------------------
20
21 class KernelClientABC(object):
22 """KernelManager ABC.
23
24 The docstrings for this class can be found in the base implementation:
25
26 `IPython.kernel.client.KernelClient`
27 """
28
29 __metaclass__ = abc.ABCMeta
30
31 @abc.abstractproperty
32 def kernel(self):
33 pass
34
35 @abc.abstractproperty
36 def shell_channel_class(self):
37 pass
38
39 @abc.abstractproperty
40 def iopub_channel_class(self):
41 pass
42
43 @abc.abstractproperty
44 def hb_channel_class(self):
45 pass
46
47 @abc.abstractproperty
48 def stdin_channel_class(self):
49 pass
50
51 #--------------------------------------------------------------------------
52 # Channel management methods
53 #--------------------------------------------------------------------------
54
55 @abc.abstractmethod
56 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
57 pass
58
59 @abc.abstractmethod
60 def stop_channels(self):
61 pass
62
63 @abc.abstractproperty
64 def channels_running(self):
65 pass
66
67 @abc.abstractproperty
68 def shell_channel(self):
69 pass
70
71 @abc.abstractproperty
72 def iopub_channel(self):
73 pass
74
75 @abc.abstractproperty
76 def stdin_channel(self):
77 pass
78
79 @abc.abstractproperty
80 def hb_channel(self):
81 pass
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,3074 +1,3074
1 1 # -*- coding: utf-8 -*-
2 2 """Main IPython class."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 from __future__ import absolute_import
18 18 from __future__ import print_function
19 19
20 20 import __builtin__ as builtin_mod
21 21 import __future__
22 22 import abc
23 23 import ast
24 24 import atexit
25 25 import os
26 26 import re
27 27 import runpy
28 28 import sys
29 29 import tempfile
30 30 import types
31 31 from io import open as io_open
32 32
33 33 from IPython.config.configurable import SingletonConfigurable
34 34 from IPython.core import debugger, oinspect
35 35 from IPython.core import magic
36 36 from IPython.core import page
37 37 from IPython.core import prefilter
38 38 from IPython.core import shadowns
39 39 from IPython.core import ultratb
40 40 from IPython.core.alias import AliasManager, AliasError
41 41 from IPython.core.autocall import ExitAutocall
42 42 from IPython.core.builtin_trap import BuiltinTrap
43 43 from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
44 44 from IPython.core.display_trap import DisplayTrap
45 45 from IPython.core.displayhook import DisplayHook
46 46 from IPython.core.displaypub import DisplayPublisher
47 47 from IPython.core.error import UsageError
48 48 from IPython.core.extensions import ExtensionManager
49 49 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
50 50 from IPython.core.formatters import DisplayFormatter
51 51 from IPython.core.history import HistoryManager
52 52 from IPython.core.inputsplitter import IPythonInputSplitter, ESC_MAGIC, ESC_MAGIC2
53 53 from IPython.core.logger import Logger
54 54 from IPython.core.macro import Macro
55 55 from IPython.core.payload import PayloadManager
56 56 from IPython.core.prefilter import PrefilterManager
57 57 from IPython.core.profiledir import ProfileDir
58 58 from IPython.core.pylabtools import pylab_activate
59 59 from IPython.core.prompts import PromptManager
60 60 from IPython.lib.latextools import LaTeXTool
61 61 from IPython.testing.skipdoctest import skip_doctest
62 62 from IPython.utils import PyColorize
63 63 from IPython.utils import io
64 64 from IPython.utils import py3compat
65 65 from IPython.utils import openpy
66 66 from IPython.utils.decorators import undoc
67 67 from IPython.utils.io import ask_yes_no
68 68 from IPython.utils.ipstruct import Struct
69 69 from IPython.utils.path import get_home_dir, get_ipython_dir, get_py_filename, unquote_filename
70 70 from IPython.utils.pickleshare import PickleShareDB
71 71 from IPython.utils.process import system, getoutput
72 72 from IPython.utils.strdispatch import StrDispatch
73 73 from IPython.utils.syspathcontext import prepended_to_syspath
74 74 from IPython.utils.text import (format_screen, LSString, SList,
75 75 DollarFormatter)
76 76 from IPython.utils.traitlets import (Integer, CBool, CaselessStrEnum, Enum,
77 77 List, Unicode, Instance, Type)
78 78 from IPython.utils.warn import warn, error
79 79 import IPython.core.hooks
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Globals
83 83 #-----------------------------------------------------------------------------
84 84
85 85 # compiled regexps for autoindent management
86 86 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Utilities
90 90 #-----------------------------------------------------------------------------
91 91
92 92 @undoc
93 93 def softspace(file, newvalue):
94 94 """Copied from code.py, to remove the dependency"""
95 95
96 96 oldvalue = 0
97 97 try:
98 98 oldvalue = file.softspace
99 99 except AttributeError:
100 100 pass
101 101 try:
102 102 file.softspace = newvalue
103 103 except (AttributeError, TypeError):
104 104 # "attribute-less object" or "read-only attributes"
105 105 pass
106 106 return oldvalue
107 107
108 108 @undoc
109 109 def no_op(*a, **kw): pass
110 110
111 111 @undoc
112 112 class NoOpContext(object):
113 113 def __enter__(self): pass
114 114 def __exit__(self, type, value, traceback): pass
115 115 no_op_context = NoOpContext()
116 116
117 117 class SpaceInInput(Exception): pass
118 118
119 119 @undoc
120 120 class Bunch: pass
121 121
122 122
123 123 def get_default_colors():
124 124 if sys.platform=='darwin':
125 125 return "LightBG"
126 126 elif os.name=='nt':
127 127 return 'Linux'
128 128 else:
129 129 return 'Linux'
130 130
131 131
132 132 class SeparateUnicode(Unicode):
133 133 """A Unicode subclass to validate separate_in, separate_out, etc.
134 134
135 135 This is a Unicode based trait that converts '0'->'' and '\\n'->'\n'.
136 136 """
137 137
138 138 def validate(self, obj, value):
139 139 if value == '0': value = ''
140 140 value = value.replace('\\n','\n')
141 141 return super(SeparateUnicode, self).validate(obj, value)
142 142
143 143
144 144 class ReadlineNoRecord(object):
145 145 """Context manager to execute some code, then reload readline history
146 146 so that interactive input to the code doesn't appear when pressing up."""
147 147 def __init__(self, shell):
148 148 self.shell = shell
149 149 self._nested_level = 0
150 150
151 151 def __enter__(self):
152 152 if self._nested_level == 0:
153 153 try:
154 154 self.orig_length = self.current_length()
155 155 self.readline_tail = self.get_readline_tail()
156 156 except (AttributeError, IndexError): # Can fail with pyreadline
157 157 self.orig_length, self.readline_tail = 999999, []
158 158 self._nested_level += 1
159 159
160 160 def __exit__(self, type, value, traceback):
161 161 self._nested_level -= 1
162 162 if self._nested_level == 0:
163 163 # Try clipping the end if it's got longer
164 164 try:
165 165 e = self.current_length() - self.orig_length
166 166 if e > 0:
167 167 for _ in range(e):
168 168 self.shell.readline.remove_history_item(self.orig_length)
169 169
170 170 # If it still doesn't match, just reload readline history.
171 171 if self.current_length() != self.orig_length \
172 172 or self.get_readline_tail() != self.readline_tail:
173 173 self.shell.refill_readline_hist()
174 174 except (AttributeError, IndexError):
175 175 pass
176 176 # Returning False will cause exceptions to propagate
177 177 return False
178 178
179 179 def current_length(self):
180 180 return self.shell.readline.get_current_history_length()
181 181
182 182 def get_readline_tail(self, n=10):
183 183 """Get the last n items in readline history."""
184 184 end = self.shell.readline.get_current_history_length() + 1
185 185 start = max(end-n, 1)
186 186 ghi = self.shell.readline.get_history_item
187 187 return [ghi(x) for x in range(start, end)]
188 188
189 189 #-----------------------------------------------------------------------------
190 190 # Main IPython class
191 191 #-----------------------------------------------------------------------------
192 192
193 193 class InteractiveShell(SingletonConfigurable):
194 194 """An enhanced, interactive shell for Python."""
195 195
196 196 _instance = None
197 197
198 198 ast_transformers = List([], config=True, help=
199 199 """
200 200 A list of ast.NodeTransformer subclass instances, which will be applied
201 201 to user input before code is run.
202 202 """
203 203 )
204 204
205 205 autocall = Enum((0,1,2), default_value=0, config=True, help=
206 206 """
207 207 Make IPython automatically call any callable object even if you didn't
208 208 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
209 209 automatically. The value can be '0' to disable the feature, '1' for
210 210 'smart' autocall, where it is not applied if there are no more
211 211 arguments on the line, and '2' for 'full' autocall, where all callable
212 212 objects are automatically called (even if no arguments are present).
213 213 """
214 214 )
215 215 # TODO: remove all autoindent logic and put into frontends.
216 216 # We can't do this yet because even runlines uses the autoindent.
217 217 autoindent = CBool(True, config=True, help=
218 218 """
219 219 Autoindent IPython code entered interactively.
220 220 """
221 221 )
222 222 automagic = CBool(True, config=True, help=
223 223 """
224 224 Enable magic commands to be called without the leading %.
225 225 """
226 226 )
227 227 cache_size = Integer(1000, config=True, help=
228 228 """
229 229 Set the size of the output cache. The default is 1000, you can
230 230 change it permanently in your config file. Setting it to 0 completely
231 231 disables the caching system, and the minimum value accepted is 20 (if
232 232 you provide a value less than 20, it is reset to 0 and a warning is
233 233 issued). This limit is defined because otherwise you'll spend more
234 234 time re-flushing a too small cache than working
235 235 """
236 236 )
237 237 color_info = CBool(True, config=True, help=
238 238 """
239 239 Use colors for displaying information about objects. Because this
240 240 information is passed through a pager (like 'less'), and some pagers
241 241 get confused with color codes, this capability can be turned off.
242 242 """
243 243 )
244 244 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
245 245 default_value=get_default_colors(), config=True,
246 246 help="Set the color scheme (NoColor, Linux, or LightBG)."
247 247 )
248 248 colors_force = CBool(False, help=
249 249 """
250 250 Force use of ANSI color codes, regardless of OS and readline
251 251 availability.
252 252 """
253 253 # FIXME: This is essentially a hack to allow ZMQShell to show colors
254 254 # without readline on Win32. When the ZMQ formatting system is
255 255 # refactored, this should be removed.
256 256 )
257 257 debug = CBool(False, config=True)
258 258 deep_reload = CBool(False, config=True, help=
259 259 """
260 260 Enable deep (recursive) reloading by default. IPython can use the
261 261 deep_reload module which reloads changes in modules recursively (it
262 262 replaces the reload() function, so you don't need to change anything to
263 263 use it). deep_reload() forces a full reload of modules whose code may
264 264 have changed, which the default reload() function does not. When
265 265 deep_reload is off, IPython will use the normal reload(), but
266 266 deep_reload will still be available as dreload().
267 267 """
268 268 )
269 269 disable_failing_post_execute = CBool(False, config=True,
270 270 help="Don't call post-execute functions that have failed in the past."
271 271 )
272 272 display_formatter = Instance(DisplayFormatter)
273 273 displayhook_class = Type(DisplayHook)
274 274 display_pub_class = Type(DisplayPublisher)
275 275 data_pub_class = None
276 276
277 277 exit_now = CBool(False)
278 278 exiter = Instance(ExitAutocall)
279 279 def _exiter_default(self):
280 280 return ExitAutocall(self)
281 281 # Monotonically increasing execution counter
282 282 execution_count = Integer(1)
283 283 filename = Unicode("<ipython console>")
284 284 ipython_dir= Unicode('', config=True) # Set to get_ipython_dir() in __init__
285 285
286 286 # Input splitter, to transform input line by line and detect when a block
287 287 # is ready to be executed.
288 288 input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
289 289 (), {'line_input_checker': True})
290 290
291 291 # This InputSplitter instance is used to transform completed cells before
292 292 # running them. It allows cell magics to contain blank lines.
293 293 input_transformer_manager = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
294 294 (), {'line_input_checker': False})
295 295
296 296 logstart = CBool(False, config=True, help=
297 297 """
298 298 Start logging to the default log file.
299 299 """
300 300 )
301 301 logfile = Unicode('', config=True, help=
302 302 """
303 303 The name of the logfile to use.
304 304 """
305 305 )
306 306 logappend = Unicode('', config=True, help=
307 307 """
308 308 Start logging to the given file in append mode.
309 309 """
310 310 )
311 311 object_info_string_level = Enum((0,1,2), default_value=0,
312 312 config=True)
313 313 pdb = CBool(False, config=True, help=
314 314 """
315 315 Automatically call the pdb debugger after every exception.
316 316 """
317 317 )
318 318 multiline_history = CBool(sys.platform != 'win32', config=True,
319 319 help="Save multi-line entries as one entry in readline history"
320 320 )
321 321
322 322 # deprecated prompt traits:
323 323
324 324 prompt_in1 = Unicode('In [\\#]: ', config=True,
325 325 help="Deprecated, use PromptManager.in_template")
326 326 prompt_in2 = Unicode(' .\\D.: ', config=True,
327 327 help="Deprecated, use PromptManager.in2_template")
328 328 prompt_out = Unicode('Out[\\#]: ', config=True,
329 329 help="Deprecated, use PromptManager.out_template")
330 330 prompts_pad_left = CBool(True, config=True,
331 331 help="Deprecated, use PromptManager.justify")
332 332
333 333 def _prompt_trait_changed(self, name, old, new):
334 334 table = {
335 335 'prompt_in1' : 'in_template',
336 336 'prompt_in2' : 'in2_template',
337 337 'prompt_out' : 'out_template',
338 338 'prompts_pad_left' : 'justify',
339 339 }
340 340 warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}".format(
341 341 name=name, newname=table[name])
342 342 )
343 343 # protect against weird cases where self.config may not exist:
344 344 if self.config is not None:
345 345 # propagate to corresponding PromptManager trait
346 346 setattr(self.config.PromptManager, table[name], new)
347 347
348 348 _prompt_in1_changed = _prompt_trait_changed
349 349 _prompt_in2_changed = _prompt_trait_changed
350 350 _prompt_out_changed = _prompt_trait_changed
351 351 _prompt_pad_left_changed = _prompt_trait_changed
352 352
353 353 show_rewritten_input = CBool(True, config=True,
354 354 help="Show rewritten input, e.g. for autocall."
355 355 )
356 356
357 357 quiet = CBool(False, config=True)
358 358
359 359 history_length = Integer(10000, config=True)
360 360
361 361 # The readline stuff will eventually be moved to the terminal subclass
362 362 # but for now, we can't do that as readline is welded in everywhere.
363 363 readline_use = CBool(True, config=True)
364 364 readline_remove_delims = Unicode('-/~', config=True)
365 365 readline_delims = Unicode() # set by init_readline()
366 366 # don't use \M- bindings by default, because they
367 367 # conflict with 8-bit encodings. See gh-58,gh-88
368 368 readline_parse_and_bind = List([
369 369 'tab: complete',
370 370 '"\C-l": clear-screen',
371 371 'set show-all-if-ambiguous on',
372 372 '"\C-o": tab-insert',
373 373 '"\C-r": reverse-search-history',
374 374 '"\C-s": forward-search-history',
375 375 '"\C-p": history-search-backward',
376 376 '"\C-n": history-search-forward',
377 377 '"\e[A": history-search-backward',
378 378 '"\e[B": history-search-forward',
379 379 '"\C-k": kill-line',
380 380 '"\C-u": unix-line-discard',
381 381 ], allow_none=False, config=True)
382 382
383 383 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none'],
384 384 default_value='last_expr', config=True,
385 385 help="""
386 386 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
387 387 run interactively (displaying output from expressions).""")
388 388
389 389 # TODO: this part of prompt management should be moved to the frontends.
390 390 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
391 391 separate_in = SeparateUnicode('\n', config=True)
392 392 separate_out = SeparateUnicode('', config=True)
393 393 separate_out2 = SeparateUnicode('', config=True)
394 394 wildcards_case_sensitive = CBool(True, config=True)
395 395 xmode = CaselessStrEnum(('Context','Plain', 'Verbose'),
396 396 default_value='Context', config=True)
397 397
398 398 # Subcomponents of InteractiveShell
399 399 alias_manager = Instance('IPython.core.alias.AliasManager')
400 400 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
401 401 builtin_trap = Instance('IPython.core.builtin_trap.BuiltinTrap')
402 402 display_trap = Instance('IPython.core.display_trap.DisplayTrap')
403 403 extension_manager = Instance('IPython.core.extensions.ExtensionManager')
404 404 payload_manager = Instance('IPython.core.payload.PayloadManager')
405 405 history_manager = Instance('IPython.core.history.HistoryManager')
406 406 magics_manager = Instance('IPython.core.magic.MagicsManager')
407 407
408 408 profile_dir = Instance('IPython.core.application.ProfileDir')
409 409 @property
410 410 def profile(self):
411 411 if self.profile_dir is not None:
412 412 name = os.path.basename(self.profile_dir.location)
413 413 return name.replace('profile_','')
414 414
415 415
416 416 # Private interface
417 417 _post_execute = Instance(dict)
418 418
419 419 # Tracks any GUI loop loaded for pylab
420 420 pylab_gui_select = None
421 421
422 422 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
423 423 user_module=None, user_ns=None,
424 custom_exceptions=((), None)):
424 custom_exceptions=((), None), **kwargs):
425 425
426 426 # This is where traits with a config_key argument are updated
427 427 # from the values on config.
428 super(InteractiveShell, self).__init__(config=config)
428 super(InteractiveShell, self).__init__(config=config, **kwargs)
429 429 self.configurables = [self]
430 430
431 431 # These are relatively independent and stateless
432 432 self.init_ipython_dir(ipython_dir)
433 433 self.init_profile_dir(profile_dir)
434 434 self.init_instance_attrs()
435 435 self.init_environment()
436 436
437 437 # Check if we're in a virtualenv, and set up sys.path.
438 438 self.init_virtualenv()
439 439
440 440 # Create namespaces (user_ns, user_global_ns, etc.)
441 441 self.init_create_namespaces(user_module, user_ns)
442 442 # This has to be done after init_create_namespaces because it uses
443 443 # something in self.user_ns, but before init_sys_modules, which
444 444 # is the first thing to modify sys.
445 445 # TODO: When we override sys.stdout and sys.stderr before this class
446 446 # is created, we are saving the overridden ones here. Not sure if this
447 447 # is what we want to do.
448 448 self.save_sys_module_state()
449 449 self.init_sys_modules()
450 450
451 451 # While we're trying to have each part of the code directly access what
452 452 # it needs without keeping redundant references to objects, we have too
453 453 # much legacy code that expects ip.db to exist.
454 454 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
455 455
456 456 self.init_history()
457 457 self.init_encoding()
458 458 self.init_prefilter()
459 459
460 460 self.init_syntax_highlighting()
461 461 self.init_hooks()
462 462 self.init_pushd_popd_magic()
463 463 # self.init_traceback_handlers use to be here, but we moved it below
464 464 # because it and init_io have to come after init_readline.
465 465 self.init_user_ns()
466 466 self.init_logger()
467 467 self.init_alias()
468 468 self.init_builtins()
469 469
470 470 # The following was in post_config_initialization
471 471 self.init_inspector()
472 472 # init_readline() must come before init_io(), because init_io uses
473 473 # readline related things.
474 474 self.init_readline()
475 475 # We save this here in case user code replaces raw_input, but it needs
476 476 # to be after init_readline(), because PyPy's readline works by replacing
477 477 # raw_input.
478 478 if py3compat.PY3:
479 479 self.raw_input_original = input
480 480 else:
481 481 self.raw_input_original = raw_input
482 482 # init_completer must come after init_readline, because it needs to
483 483 # know whether readline is present or not system-wide to configure the
484 484 # completers, since the completion machinery can now operate
485 485 # independently of readline (e.g. over the network)
486 486 self.init_completer()
487 487 # TODO: init_io() needs to happen before init_traceback handlers
488 488 # because the traceback handlers hardcode the stdout/stderr streams.
489 489 # This logic in in debugger.Pdb and should eventually be changed.
490 490 self.init_io()
491 491 self.init_traceback_handlers(custom_exceptions)
492 492 self.init_prompts()
493 493 self.init_display_formatter()
494 494 self.init_display_pub()
495 495 self.init_data_pub()
496 496 self.init_displayhook()
497 497 self.init_latextool()
498 498 self.init_magics()
499 499 self.init_logstart()
500 500 self.init_pdb()
501 501 self.init_extension_manager()
502 502 self.init_payload()
503 503 self.hooks.late_startup_hook()
504 504 atexit.register(self.atexit_operations)
505 505
506 506 def get_ipython(self):
507 507 """Return the currently running IPython instance."""
508 508 return self
509 509
510 510 #-------------------------------------------------------------------------
511 511 # Trait changed handlers
512 512 #-------------------------------------------------------------------------
513 513
514 514 def _ipython_dir_changed(self, name, new):
515 515 if not os.path.isdir(new):
516 516 os.makedirs(new, mode = 0o777)
517 517
518 518 def set_autoindent(self,value=None):
519 519 """Set the autoindent flag, checking for readline support.
520 520
521 521 If called with no arguments, it acts as a toggle."""
522 522
523 523 if value != 0 and not self.has_readline:
524 524 if os.name == 'posix':
525 525 warn("The auto-indent feature requires the readline library")
526 526 self.autoindent = 0
527 527 return
528 528 if value is None:
529 529 self.autoindent = not self.autoindent
530 530 else:
531 531 self.autoindent = value
532 532
533 533 #-------------------------------------------------------------------------
534 534 # init_* methods called by __init__
535 535 #-------------------------------------------------------------------------
536 536
537 537 def init_ipython_dir(self, ipython_dir):
538 538 if ipython_dir is not None:
539 539 self.ipython_dir = ipython_dir
540 540 return
541 541
542 542 self.ipython_dir = get_ipython_dir()
543 543
544 544 def init_profile_dir(self, profile_dir):
545 545 if profile_dir is not None:
546 546 self.profile_dir = profile_dir
547 547 return
548 548 self.profile_dir =\
549 549 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
550 550
551 551 def init_instance_attrs(self):
552 552 self.more = False
553 553
554 554 # command compiler
555 555 self.compile = CachingCompiler()
556 556
557 557 # Make an empty namespace, which extension writers can rely on both
558 558 # existing and NEVER being used by ipython itself. This gives them a
559 559 # convenient location for storing additional information and state
560 560 # their extensions may require, without fear of collisions with other
561 561 # ipython names that may develop later.
562 562 self.meta = Struct()
563 563
564 564 # Temporary files used for various purposes. Deleted at exit.
565 565 self.tempfiles = []
566 566
567 567 # Keep track of readline usage (later set by init_readline)
568 568 self.has_readline = False
569 569
570 570 # keep track of where we started running (mainly for crash post-mortem)
571 571 # This is not being used anywhere currently.
572 572 self.starting_dir = os.getcwdu()
573 573
574 574 # Indentation management
575 575 self.indent_current_nsp = 0
576 576
577 577 # Dict to track post-execution functions that have been registered
578 578 self._post_execute = {}
579 579
580 580 def init_environment(self):
581 581 """Any changes we need to make to the user's environment."""
582 582 pass
583 583
584 584 def init_encoding(self):
585 585 # Get system encoding at startup time. Certain terminals (like Emacs
586 586 # under Win32 have it set to None, and we need to have a known valid
587 587 # encoding to use in the raw_input() method
588 588 try:
589 589 self.stdin_encoding = sys.stdin.encoding or 'ascii'
590 590 except AttributeError:
591 591 self.stdin_encoding = 'ascii'
592 592
593 593 def init_syntax_highlighting(self):
594 594 # Python source parser/formatter for syntax highlighting
595 595 pyformat = PyColorize.Parser().format
596 596 self.pycolorize = lambda src: pyformat(src,'str',self.colors)
597 597
598 598 def init_pushd_popd_magic(self):
599 599 # for pushd/popd management
600 600 self.home_dir = get_home_dir()
601 601
602 602 self.dir_stack = []
603 603
604 604 def init_logger(self):
605 605 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
606 606 logmode='rotate')
607 607
608 608 def init_logstart(self):
609 609 """Initialize logging in case it was requested at the command line.
610 610 """
611 611 if self.logappend:
612 612 self.magic('logstart %s append' % self.logappend)
613 613 elif self.logfile:
614 614 self.magic('logstart %s' % self.logfile)
615 615 elif self.logstart:
616 616 self.magic('logstart')
617 617
618 618 def init_builtins(self):
619 619 # A single, static flag that we set to True. Its presence indicates
620 620 # that an IPython shell has been created, and we make no attempts at
621 621 # removing on exit or representing the existence of more than one
622 622 # IPython at a time.
623 623 builtin_mod.__dict__['__IPYTHON__'] = True
624 624
625 625 # In 0.11 we introduced '__IPYTHON__active' as an integer we'd try to
626 626 # manage on enter/exit, but with all our shells it's virtually
627 627 # impossible to get all the cases right. We're leaving the name in for
628 628 # those who adapted their codes to check for this flag, but will
629 629 # eventually remove it after a few more releases.
630 630 builtin_mod.__dict__['__IPYTHON__active'] = \
631 631 'Deprecated, check for __IPYTHON__'
632 632
633 633 self.builtin_trap = BuiltinTrap(shell=self)
634 634
635 635 def init_inspector(self):
636 636 # Object inspector
637 637 self.inspector = oinspect.Inspector(oinspect.InspectColors,
638 638 PyColorize.ANSICodeColors,
639 639 'NoColor',
640 640 self.object_info_string_level)
641 641
642 642 def init_io(self):
643 643 # This will just use sys.stdout and sys.stderr. If you want to
644 644 # override sys.stdout and sys.stderr themselves, you need to do that
645 645 # *before* instantiating this class, because io holds onto
646 646 # references to the underlying streams.
647 647 if (sys.platform == 'win32' or sys.platform == 'cli') and self.has_readline:
648 648 io.stdout = io.stderr = io.IOStream(self.readline._outputfile)
649 649 else:
650 650 io.stdout = io.IOStream(sys.stdout)
651 651 io.stderr = io.IOStream(sys.stderr)
652 652
653 653 def init_prompts(self):
654 654 self.prompt_manager = PromptManager(shell=self, config=self.config)
655 655 self.configurables.append(self.prompt_manager)
656 656 # Set system prompts, so that scripts can decide if they are running
657 657 # interactively.
658 658 sys.ps1 = 'In : '
659 659 sys.ps2 = '...: '
660 660 sys.ps3 = 'Out: '
661 661
662 662 def init_display_formatter(self):
663 663 self.display_formatter = DisplayFormatter(config=self.config)
664 664 self.configurables.append(self.display_formatter)
665 665
666 666 def init_display_pub(self):
667 667 self.display_pub = self.display_pub_class(config=self.config)
668 668 self.configurables.append(self.display_pub)
669 669
670 670 def init_data_pub(self):
671 671 if not self.data_pub_class:
672 672 self.data_pub = None
673 673 return
674 674 self.data_pub = self.data_pub_class(config=self.config)
675 675 self.configurables.append(self.data_pub)
676 676
677 677 def init_displayhook(self):
678 678 # Initialize displayhook, set in/out prompts and printing system
679 679 self.displayhook = self.displayhook_class(
680 680 config=self.config,
681 681 shell=self,
682 682 cache_size=self.cache_size,
683 683 )
684 684 self.configurables.append(self.displayhook)
685 685 # This is a context manager that installs/revmoes the displayhook at
686 686 # the appropriate time.
687 687 self.display_trap = DisplayTrap(hook=self.displayhook)
688 688
689 689 def init_latextool(self):
690 690 """Configure LaTeXTool."""
691 691 cfg = LaTeXTool.instance(config=self.config)
692 692 if cfg not in self.configurables:
693 693 self.configurables.append(cfg)
694 694
695 695 def init_virtualenv(self):
696 696 """Add a virtualenv to sys.path so the user can import modules from it.
697 697 This isn't perfect: it doesn't use the Python interpreter with which the
698 698 virtualenv was built, and it ignores the --no-site-packages option. A
699 699 warning will appear suggesting the user installs IPython in the
700 700 virtualenv, but for many cases, it probably works well enough.
701 701
702 702 Adapted from code snippets online.
703 703
704 704 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
705 705 """
706 706 if 'VIRTUAL_ENV' not in os.environ:
707 707 # Not in a virtualenv
708 708 return
709 709
710 710 if sys.executable.startswith(os.environ['VIRTUAL_ENV']):
711 711 # Running properly in the virtualenv, don't need to do anything
712 712 return
713 713
714 714 warn("Attempting to work in a virtualenv. If you encounter problems, please "
715 715 "install IPython inside the virtualenv.")
716 716 if sys.platform == "win32":
717 717 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'Lib', 'site-packages')
718 718 else:
719 719 virtual_env = os.path.join(os.environ['VIRTUAL_ENV'], 'lib',
720 720 'python%d.%d' % sys.version_info[:2], 'site-packages')
721 721
722 722 import site
723 723 sys.path.insert(0, virtual_env)
724 724 site.addsitedir(virtual_env)
725 725
726 726 #-------------------------------------------------------------------------
727 727 # Things related to injections into the sys module
728 728 #-------------------------------------------------------------------------
729 729
730 730 def save_sys_module_state(self):
731 731 """Save the state of hooks in the sys module.
732 732
733 733 This has to be called after self.user_module is created.
734 734 """
735 735 self._orig_sys_module_state = {}
736 736 self._orig_sys_module_state['stdin'] = sys.stdin
737 737 self._orig_sys_module_state['stdout'] = sys.stdout
738 738 self._orig_sys_module_state['stderr'] = sys.stderr
739 739 self._orig_sys_module_state['excepthook'] = sys.excepthook
740 740 self._orig_sys_modules_main_name = self.user_module.__name__
741 741 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
742 742
743 743 def restore_sys_module_state(self):
744 744 """Restore the state of the sys module."""
745 745 try:
746 746 for k, v in self._orig_sys_module_state.iteritems():
747 747 setattr(sys, k, v)
748 748 except AttributeError:
749 749 pass
750 750 # Reset what what done in self.init_sys_modules
751 751 if self._orig_sys_modules_main_mod is not None:
752 752 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
753 753
754 754 #-------------------------------------------------------------------------
755 755 # Things related to hooks
756 756 #-------------------------------------------------------------------------
757 757
758 758 def init_hooks(self):
759 759 # hooks holds pointers used for user-side customizations
760 760 self.hooks = Struct()
761 761
762 762 self.strdispatchers = {}
763 763
764 764 # Set all default hooks, defined in the IPython.hooks module.
765 765 hooks = IPython.core.hooks
766 766 for hook_name in hooks.__all__:
767 767 # default hooks have priority 100, i.e. low; user hooks should have
768 768 # 0-100 priority
769 769 self.set_hook(hook_name,getattr(hooks,hook_name), 100)
770 770
771 771 def set_hook(self,name,hook, priority = 50, str_key = None, re_key = None):
772 772 """set_hook(name,hook) -> sets an internal IPython hook.
773 773
774 774 IPython exposes some of its internal API as user-modifiable hooks. By
775 775 adding your function to one of these hooks, you can modify IPython's
776 776 behavior to call at runtime your own routines."""
777 777
778 778 # At some point in the future, this should validate the hook before it
779 779 # accepts it. Probably at least check that the hook takes the number
780 780 # of args it's supposed to.
781 781
782 782 f = types.MethodType(hook,self)
783 783
784 784 # check if the hook is for strdispatcher first
785 785 if str_key is not None:
786 786 sdp = self.strdispatchers.get(name, StrDispatch())
787 787 sdp.add_s(str_key, f, priority )
788 788 self.strdispatchers[name] = sdp
789 789 return
790 790 if re_key is not None:
791 791 sdp = self.strdispatchers.get(name, StrDispatch())
792 792 sdp.add_re(re.compile(re_key), f, priority )
793 793 self.strdispatchers[name] = sdp
794 794 return
795 795
796 796 dp = getattr(self.hooks, name, None)
797 797 if name not in IPython.core.hooks.__all__:
798 798 print("Warning! Hook '%s' is not one of %s" % \
799 799 (name, IPython.core.hooks.__all__ ))
800 800 if not dp:
801 801 dp = IPython.core.hooks.CommandChainDispatcher()
802 802
803 803 try:
804 804 dp.add(f,priority)
805 805 except AttributeError:
806 806 # it was not commandchain, plain old func - replace
807 807 dp = f
808 808
809 809 setattr(self.hooks,name, dp)
810 810
811 811 def register_post_execute(self, func):
812 812 """Register a function for calling after code execution.
813 813 """
814 814 if not callable(func):
815 815 raise ValueError('argument %s must be callable' % func)
816 816 self._post_execute[func] = True
817 817
818 818 #-------------------------------------------------------------------------
819 819 # Things related to the "main" module
820 820 #-------------------------------------------------------------------------
821 821
822 822 def new_main_mod(self,ns=None):
823 823 """Return a new 'main' module object for user code execution.
824 824 """
825 825 main_mod = self._user_main_module
826 826 init_fakemod_dict(main_mod,ns)
827 827 return main_mod
828 828
829 829 def cache_main_mod(self,ns,fname):
830 830 """Cache a main module's namespace.
831 831
832 832 When scripts are executed via %run, we must keep a reference to the
833 833 namespace of their __main__ module (a FakeModule instance) around so
834 834 that Python doesn't clear it, rendering objects defined therein
835 835 useless.
836 836
837 837 This method keeps said reference in a private dict, keyed by the
838 838 absolute path of the module object (which corresponds to the script
839 839 path). This way, for multiple executions of the same script we only
840 840 keep one copy of the namespace (the last one), thus preventing memory
841 841 leaks from old references while allowing the objects from the last
842 842 execution to be accessible.
843 843
844 844 Note: we can not allow the actual FakeModule instances to be deleted,
845 845 because of how Python tears down modules (it hard-sets all their
846 846 references to None without regard for reference counts). This method
847 847 must therefore make a *copy* of the given namespace, to allow the
848 848 original module's __dict__ to be cleared and reused.
849 849
850 850
851 851 Parameters
852 852 ----------
853 853 ns : a namespace (a dict, typically)
854 854
855 855 fname : str
856 856 Filename associated with the namespace.
857 857
858 858 Examples
859 859 --------
860 860
861 861 In [10]: import IPython
862 862
863 863 In [11]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__)
864 864
865 865 In [12]: IPython.__file__ in _ip._main_ns_cache
866 866 Out[12]: True
867 867 """
868 868 self._main_ns_cache[os.path.abspath(fname)] = ns.copy()
869 869
870 870 def clear_main_mod_cache(self):
871 871 """Clear the cache of main modules.
872 872
873 873 Mainly for use by utilities like %reset.
874 874
875 875 Examples
876 876 --------
877 877
878 878 In [15]: import IPython
879 879
880 880 In [16]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__)
881 881
882 882 In [17]: len(_ip._main_ns_cache) > 0
883 883 Out[17]: True
884 884
885 885 In [18]: _ip.clear_main_mod_cache()
886 886
887 887 In [19]: len(_ip._main_ns_cache) == 0
888 888 Out[19]: True
889 889 """
890 890 self._main_ns_cache.clear()
891 891
892 892 #-------------------------------------------------------------------------
893 893 # Things related to debugging
894 894 #-------------------------------------------------------------------------
895 895
896 896 def init_pdb(self):
897 897 # Set calling of pdb on exceptions
898 898 # self.call_pdb is a property
899 899 self.call_pdb = self.pdb
900 900
901 901 def _get_call_pdb(self):
902 902 return self._call_pdb
903 903
904 904 def _set_call_pdb(self,val):
905 905
906 906 if val not in (0,1,False,True):
907 907 raise ValueError('new call_pdb value must be boolean')
908 908
909 909 # store value in instance
910 910 self._call_pdb = val
911 911
912 912 # notify the actual exception handlers
913 913 self.InteractiveTB.call_pdb = val
914 914
915 915 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
916 916 'Control auto-activation of pdb at exceptions')
917 917
918 918 def debugger(self,force=False):
919 919 """Call the pydb/pdb debugger.
920 920
921 921 Keywords:
922 922
923 923 - force(False): by default, this routine checks the instance call_pdb
924 924 flag and does not actually invoke the debugger if the flag is false.
925 925 The 'force' option forces the debugger to activate even if the flag
926 926 is false.
927 927 """
928 928
929 929 if not (force or self.call_pdb):
930 930 return
931 931
932 932 if not hasattr(sys,'last_traceback'):
933 933 error('No traceback has been produced, nothing to debug.')
934 934 return
935 935
936 936 # use pydb if available
937 937 if debugger.has_pydb:
938 938 from pydb import pm
939 939 else:
940 940 # fallback to our internal debugger
941 941 pm = lambda : self.InteractiveTB.debugger(force=True)
942 942
943 943 with self.readline_no_record:
944 944 pm()
945 945
946 946 #-------------------------------------------------------------------------
947 947 # Things related to IPython's various namespaces
948 948 #-------------------------------------------------------------------------
949 949 default_user_namespaces = True
950 950
951 951 def init_create_namespaces(self, user_module=None, user_ns=None):
952 952 # Create the namespace where the user will operate. user_ns is
953 953 # normally the only one used, and it is passed to the exec calls as
954 954 # the locals argument. But we do carry a user_global_ns namespace
955 955 # given as the exec 'globals' argument, This is useful in embedding
956 956 # situations where the ipython shell opens in a context where the
957 957 # distinction between locals and globals is meaningful. For
958 958 # non-embedded contexts, it is just the same object as the user_ns dict.
959 959
960 960 # FIXME. For some strange reason, __builtins__ is showing up at user
961 961 # level as a dict instead of a module. This is a manual fix, but I
962 962 # should really track down where the problem is coming from. Alex
963 963 # Schmolck reported this problem first.
964 964
965 965 # A useful post by Alex Martelli on this topic:
966 966 # Re: inconsistent value from __builtins__
967 967 # Von: Alex Martelli <aleaxit@yahoo.com>
968 968 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
969 969 # Gruppen: comp.lang.python
970 970
971 971 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
972 972 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
973 973 # > <type 'dict'>
974 974 # > >>> print type(__builtins__)
975 975 # > <type 'module'>
976 976 # > Is this difference in return value intentional?
977 977
978 978 # Well, it's documented that '__builtins__' can be either a dictionary
979 979 # or a module, and it's been that way for a long time. Whether it's
980 980 # intentional (or sensible), I don't know. In any case, the idea is
981 981 # that if you need to access the built-in namespace directly, you
982 982 # should start with "import __builtin__" (note, no 's') which will
983 983 # definitely give you a module. Yeah, it's somewhat confusing:-(.
984 984
985 985 # These routines return a properly built module and dict as needed by
986 986 # the rest of the code, and can also be used by extension writers to
987 987 # generate properly initialized namespaces.
988 988 if (user_ns is not None) or (user_module is not None):
989 989 self.default_user_namespaces = False
990 990 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
991 991
992 992 # A record of hidden variables we have added to the user namespace, so
993 993 # we can list later only variables defined in actual interactive use.
994 994 self.user_ns_hidden = set()
995 995
996 996 # Now that FakeModule produces a real module, we've run into a nasty
997 997 # problem: after script execution (via %run), the module where the user
998 998 # code ran is deleted. Now that this object is a true module (needed
999 999 # so docetst and other tools work correctly), the Python module
1000 1000 # teardown mechanism runs over it, and sets to None every variable
1001 1001 # present in that module. Top-level references to objects from the
1002 1002 # script survive, because the user_ns is updated with them. However,
1003 1003 # calling functions defined in the script that use other things from
1004 1004 # the script will fail, because the function's closure had references
1005 1005 # to the original objects, which are now all None. So we must protect
1006 1006 # these modules from deletion by keeping a cache.
1007 1007 #
1008 1008 # To avoid keeping stale modules around (we only need the one from the
1009 1009 # last run), we use a dict keyed with the full path to the script, so
1010 1010 # only the last version of the module is held in the cache. Note,
1011 1011 # however, that we must cache the module *namespace contents* (their
1012 1012 # __dict__). Because if we try to cache the actual modules, old ones
1013 1013 # (uncached) could be destroyed while still holding references (such as
1014 1014 # those held by GUI objects that tend to be long-lived)>
1015 1015 #
1016 1016 # The %reset command will flush this cache. See the cache_main_mod()
1017 1017 # and clear_main_mod_cache() methods for details on use.
1018 1018
1019 1019 # This is the cache used for 'main' namespaces
1020 1020 self._main_ns_cache = {}
1021 1021 # And this is the single instance of FakeModule whose __dict__ we keep
1022 1022 # copying and clearing for reuse on each %run
1023 1023 self._user_main_module = FakeModule()
1024 1024
1025 1025 # A table holding all the namespaces IPython deals with, so that
1026 1026 # introspection facilities can search easily.
1027 1027 self.ns_table = {'user_global':self.user_module.__dict__,
1028 1028 'user_local':self.user_ns,
1029 1029 'builtin':builtin_mod.__dict__
1030 1030 }
1031 1031
1032 1032 @property
1033 1033 def user_global_ns(self):
1034 1034 return self.user_module.__dict__
1035 1035
1036 1036 def prepare_user_module(self, user_module=None, user_ns=None):
1037 1037 """Prepare the module and namespace in which user code will be run.
1038 1038
1039 1039 When IPython is started normally, both parameters are None: a new module
1040 1040 is created automatically, and its __dict__ used as the namespace.
1041 1041
1042 1042 If only user_module is provided, its __dict__ is used as the namespace.
1043 1043 If only user_ns is provided, a dummy module is created, and user_ns
1044 1044 becomes the global namespace. If both are provided (as they may be
1045 1045 when embedding), user_ns is the local namespace, and user_module
1046 1046 provides the global namespace.
1047 1047
1048 1048 Parameters
1049 1049 ----------
1050 1050 user_module : module, optional
1051 1051 The current user module in which IPython is being run. If None,
1052 1052 a clean module will be created.
1053 1053 user_ns : dict, optional
1054 1054 A namespace in which to run interactive commands.
1055 1055
1056 1056 Returns
1057 1057 -------
1058 1058 A tuple of user_module and user_ns, each properly initialised.
1059 1059 """
1060 1060 if user_module is None and user_ns is not None:
1061 1061 user_ns.setdefault("__name__", "__main__")
1062 1062 class DummyMod(object):
1063 1063 "A dummy module used for IPython's interactive namespace."
1064 1064 pass
1065 1065 user_module = DummyMod()
1066 1066 user_module.__dict__ = user_ns
1067 1067
1068 1068 if user_module is None:
1069 1069 user_module = types.ModuleType("__main__",
1070 1070 doc="Automatically created module for IPython interactive environment")
1071 1071
1072 1072 # We must ensure that __builtin__ (without the final 's') is always
1073 1073 # available and pointing to the __builtin__ *module*. For more details:
1074 1074 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1075 1075 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1076 1076 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1077 1077
1078 1078 if user_ns is None:
1079 1079 user_ns = user_module.__dict__
1080 1080
1081 1081 return user_module, user_ns
1082 1082
1083 1083 def init_sys_modules(self):
1084 1084 # We need to insert into sys.modules something that looks like a
1085 1085 # module but which accesses the IPython namespace, for shelve and
1086 1086 # pickle to work interactively. Normally they rely on getting
1087 1087 # everything out of __main__, but for embedding purposes each IPython
1088 1088 # instance has its own private namespace, so we can't go shoving
1089 1089 # everything into __main__.
1090 1090
1091 1091 # note, however, that we should only do this for non-embedded
1092 1092 # ipythons, which really mimic the __main__.__dict__ with their own
1093 1093 # namespace. Embedded instances, on the other hand, should not do
1094 1094 # this because they need to manage the user local/global namespaces
1095 1095 # only, but they live within a 'normal' __main__ (meaning, they
1096 1096 # shouldn't overtake the execution environment of the script they're
1097 1097 # embedded in).
1098 1098
1099 1099 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1100 1100 main_name = self.user_module.__name__
1101 1101 sys.modules[main_name] = self.user_module
1102 1102
1103 1103 def init_user_ns(self):
1104 1104 """Initialize all user-visible namespaces to their minimum defaults.
1105 1105
1106 1106 Certain history lists are also initialized here, as they effectively
1107 1107 act as user namespaces.
1108 1108
1109 1109 Notes
1110 1110 -----
1111 1111 All data structures here are only filled in, they are NOT reset by this
1112 1112 method. If they were not empty before, data will simply be added to
1113 1113 therm.
1114 1114 """
1115 1115 # This function works in two parts: first we put a few things in
1116 1116 # user_ns, and we sync that contents into user_ns_hidden so that these
1117 1117 # initial variables aren't shown by %who. After the sync, we add the
1118 1118 # rest of what we *do* want the user to see with %who even on a new
1119 1119 # session (probably nothing, so theye really only see their own stuff)
1120 1120
1121 1121 # The user dict must *always* have a __builtin__ reference to the
1122 1122 # Python standard __builtin__ namespace, which must be imported.
1123 1123 # This is so that certain operations in prompt evaluation can be
1124 1124 # reliably executed with builtins. Note that we can NOT use
1125 1125 # __builtins__ (note the 's'), because that can either be a dict or a
1126 1126 # module, and can even mutate at runtime, depending on the context
1127 1127 # (Python makes no guarantees on it). In contrast, __builtin__ is
1128 1128 # always a module object, though it must be explicitly imported.
1129 1129
1130 1130 # For more details:
1131 1131 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1132 1132 ns = dict()
1133 1133
1134 1134 # Put 'help' in the user namespace
1135 1135 try:
1136 1136 from site import _Helper
1137 1137 ns['help'] = _Helper()
1138 1138 except ImportError:
1139 1139 warn('help() not available - check site.py')
1140 1140
1141 1141 # make global variables for user access to the histories
1142 1142 ns['_ih'] = self.history_manager.input_hist_parsed
1143 1143 ns['_oh'] = self.history_manager.output_hist
1144 1144 ns['_dh'] = self.history_manager.dir_hist
1145 1145
1146 1146 ns['_sh'] = shadowns
1147 1147
1148 1148 # user aliases to input and output histories. These shouldn't show up
1149 1149 # in %who, as they can have very large reprs.
1150 1150 ns['In'] = self.history_manager.input_hist_parsed
1151 1151 ns['Out'] = self.history_manager.output_hist
1152 1152
1153 1153 # Store myself as the public api!!!
1154 1154 ns['get_ipython'] = self.get_ipython
1155 1155
1156 1156 ns['exit'] = self.exiter
1157 1157 ns['quit'] = self.exiter
1158 1158
1159 1159 # Sync what we've added so far to user_ns_hidden so these aren't seen
1160 1160 # by %who
1161 1161 self.user_ns_hidden.update(ns)
1162 1162
1163 1163 # Anything put into ns now would show up in %who. Think twice before
1164 1164 # putting anything here, as we really want %who to show the user their
1165 1165 # stuff, not our variables.
1166 1166
1167 1167 # Finally, update the real user's namespace
1168 1168 self.user_ns.update(ns)
1169 1169
1170 1170 @property
1171 1171 def all_ns_refs(self):
1172 1172 """Get a list of references to all the namespace dictionaries in which
1173 1173 IPython might store a user-created object.
1174 1174
1175 1175 Note that this does not include the displayhook, which also caches
1176 1176 objects from the output."""
1177 1177 return [self.user_ns, self.user_global_ns,
1178 1178 self._user_main_module.__dict__] + self._main_ns_cache.values()
1179 1179
1180 1180 def reset(self, new_session=True):
1181 1181 """Clear all internal namespaces, and attempt to release references to
1182 1182 user objects.
1183 1183
1184 1184 If new_session is True, a new history session will be opened.
1185 1185 """
1186 1186 # Clear histories
1187 1187 self.history_manager.reset(new_session)
1188 1188 # Reset counter used to index all histories
1189 1189 if new_session:
1190 1190 self.execution_count = 1
1191 1191
1192 1192 # Flush cached output items
1193 1193 if self.displayhook.do_full_cache:
1194 1194 self.displayhook.flush()
1195 1195
1196 1196 # The main execution namespaces must be cleared very carefully,
1197 1197 # skipping the deletion of the builtin-related keys, because doing so
1198 1198 # would cause errors in many object's __del__ methods.
1199 1199 if self.user_ns is not self.user_global_ns:
1200 1200 self.user_ns.clear()
1201 1201 ns = self.user_global_ns
1202 1202 drop_keys = set(ns.keys())
1203 1203 drop_keys.discard('__builtin__')
1204 1204 drop_keys.discard('__builtins__')
1205 1205 drop_keys.discard('__name__')
1206 1206 for k in drop_keys:
1207 1207 del ns[k]
1208 1208
1209 1209 self.user_ns_hidden.clear()
1210 1210
1211 1211 # Restore the user namespaces to minimal usability
1212 1212 self.init_user_ns()
1213 1213
1214 1214 # Restore the default and user aliases
1215 1215 self.alias_manager.clear_aliases()
1216 1216 self.alias_manager.init_aliases()
1217 1217
1218 1218 # Flush the private list of module references kept for script
1219 1219 # execution protection
1220 1220 self.clear_main_mod_cache()
1221 1221
1222 1222 # Clear out the namespace from the last %run
1223 1223 self.new_main_mod()
1224 1224
1225 1225 def del_var(self, varname, by_name=False):
1226 1226 """Delete a variable from the various namespaces, so that, as
1227 1227 far as possible, we're not keeping any hidden references to it.
1228 1228
1229 1229 Parameters
1230 1230 ----------
1231 1231 varname : str
1232 1232 The name of the variable to delete.
1233 1233 by_name : bool
1234 1234 If True, delete variables with the given name in each
1235 1235 namespace. If False (default), find the variable in the user
1236 1236 namespace, and delete references to it.
1237 1237 """
1238 1238 if varname in ('__builtin__', '__builtins__'):
1239 1239 raise ValueError("Refusing to delete %s" % varname)
1240 1240
1241 1241 ns_refs = self.all_ns_refs
1242 1242
1243 1243 if by_name: # Delete by name
1244 1244 for ns in ns_refs:
1245 1245 try:
1246 1246 del ns[varname]
1247 1247 except KeyError:
1248 1248 pass
1249 1249 else: # Delete by object
1250 1250 try:
1251 1251 obj = self.user_ns[varname]
1252 1252 except KeyError:
1253 1253 raise NameError("name '%s' is not defined" % varname)
1254 1254 # Also check in output history
1255 1255 ns_refs.append(self.history_manager.output_hist)
1256 1256 for ns in ns_refs:
1257 1257 to_delete = [n for n, o in ns.iteritems() if o is obj]
1258 1258 for name in to_delete:
1259 1259 del ns[name]
1260 1260
1261 1261 # displayhook keeps extra references, but not in a dictionary
1262 1262 for name in ('_', '__', '___'):
1263 1263 if getattr(self.displayhook, name) is obj:
1264 1264 setattr(self.displayhook, name, None)
1265 1265
1266 1266 def reset_selective(self, regex=None):
1267 1267 """Clear selective variables from internal namespaces based on a
1268 1268 specified regular expression.
1269 1269
1270 1270 Parameters
1271 1271 ----------
1272 1272 regex : string or compiled pattern, optional
1273 1273 A regular expression pattern that will be used in searching
1274 1274 variable names in the users namespaces.
1275 1275 """
1276 1276 if regex is not None:
1277 1277 try:
1278 1278 m = re.compile(regex)
1279 1279 except TypeError:
1280 1280 raise TypeError('regex must be a string or compiled pattern')
1281 1281 # Search for keys in each namespace that match the given regex
1282 1282 # If a match is found, delete the key/value pair.
1283 1283 for ns in self.all_ns_refs:
1284 1284 for var in ns:
1285 1285 if m.search(var):
1286 1286 del ns[var]
1287 1287
1288 1288 def push(self, variables, interactive=True):
1289 1289 """Inject a group of variables into the IPython user namespace.
1290 1290
1291 1291 Parameters
1292 1292 ----------
1293 1293 variables : dict, str or list/tuple of str
1294 1294 The variables to inject into the user's namespace. If a dict, a
1295 1295 simple update is done. If a str, the string is assumed to have
1296 1296 variable names separated by spaces. A list/tuple of str can also
1297 1297 be used to give the variable names. If just the variable names are
1298 1298 give (list/tuple/str) then the variable values looked up in the
1299 1299 callers frame.
1300 1300 interactive : bool
1301 1301 If True (default), the variables will be listed with the ``who``
1302 1302 magic.
1303 1303 """
1304 1304 vdict = None
1305 1305
1306 1306 # We need a dict of name/value pairs to do namespace updates.
1307 1307 if isinstance(variables, dict):
1308 1308 vdict = variables
1309 1309 elif isinstance(variables, (basestring, list, tuple)):
1310 1310 if isinstance(variables, basestring):
1311 1311 vlist = variables.split()
1312 1312 else:
1313 1313 vlist = variables
1314 1314 vdict = {}
1315 1315 cf = sys._getframe(1)
1316 1316 for name in vlist:
1317 1317 try:
1318 1318 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1319 1319 except:
1320 1320 print('Could not get variable %s from %s' %
1321 1321 (name,cf.f_code.co_name))
1322 1322 else:
1323 1323 raise ValueError('variables must be a dict/str/list/tuple')
1324 1324
1325 1325 # Propagate variables to user namespace
1326 1326 self.user_ns.update(vdict)
1327 1327
1328 1328 # And configure interactive visibility
1329 1329 user_ns_hidden = self.user_ns_hidden
1330 1330 if interactive:
1331 1331 user_ns_hidden.difference_update(vdict)
1332 1332 else:
1333 1333 user_ns_hidden.update(vdict)
1334 1334
1335 1335 def drop_by_id(self, variables):
1336 1336 """Remove a dict of variables from the user namespace, if they are the
1337 1337 same as the values in the dictionary.
1338 1338
1339 1339 This is intended for use by extensions: variables that they've added can
1340 1340 be taken back out if they are unloaded, without removing any that the
1341 1341 user has overwritten.
1342 1342
1343 1343 Parameters
1344 1344 ----------
1345 1345 variables : dict
1346 1346 A dictionary mapping object names (as strings) to the objects.
1347 1347 """
1348 1348 for name, obj in variables.iteritems():
1349 1349 if name in self.user_ns and self.user_ns[name] is obj:
1350 1350 del self.user_ns[name]
1351 1351 self.user_ns_hidden.discard(name)
1352 1352
1353 1353 #-------------------------------------------------------------------------
1354 1354 # Things related to object introspection
1355 1355 #-------------------------------------------------------------------------
1356 1356
1357 1357 def _ofind(self, oname, namespaces=None):
1358 1358 """Find an object in the available namespaces.
1359 1359
1360 1360 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1361 1361
1362 1362 Has special code to detect magic functions.
1363 1363 """
1364 1364 oname = oname.strip()
1365 1365 #print '1- oname: <%r>' % oname # dbg
1366 1366 if not oname.startswith(ESC_MAGIC) and \
1367 1367 not oname.startswith(ESC_MAGIC2) and \
1368 1368 not py3compat.isidentifier(oname, dotted=True):
1369 1369 return dict(found=False)
1370 1370
1371 1371 alias_ns = None
1372 1372 if namespaces is None:
1373 1373 # Namespaces to search in:
1374 1374 # Put them in a list. The order is important so that we
1375 1375 # find things in the same order that Python finds them.
1376 1376 namespaces = [ ('Interactive', self.user_ns),
1377 1377 ('Interactive (global)', self.user_global_ns),
1378 1378 ('Python builtin', builtin_mod.__dict__),
1379 1379 ('Alias', self.alias_manager.alias_table),
1380 1380 ]
1381 1381 alias_ns = self.alias_manager.alias_table
1382 1382
1383 1383 # initialize results to 'null'
1384 1384 found = False; obj = None; ospace = None; ds = None;
1385 1385 ismagic = False; isalias = False; parent = None
1386 1386
1387 1387 # We need to special-case 'print', which as of python2.6 registers as a
1388 1388 # function but should only be treated as one if print_function was
1389 1389 # loaded with a future import. In this case, just bail.
1390 1390 if (oname == 'print' and not py3compat.PY3 and not \
1391 1391 (self.compile.compiler_flags & __future__.CO_FUTURE_PRINT_FUNCTION)):
1392 1392 return {'found':found, 'obj':obj, 'namespace':ospace,
1393 1393 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1394 1394
1395 1395 # Look for the given name by splitting it in parts. If the head is
1396 1396 # found, then we look for all the remaining parts as members, and only
1397 1397 # declare success if we can find them all.
1398 1398 oname_parts = oname.split('.')
1399 1399 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1400 1400 for nsname,ns in namespaces:
1401 1401 try:
1402 1402 obj = ns[oname_head]
1403 1403 except KeyError:
1404 1404 continue
1405 1405 else:
1406 1406 #print 'oname_rest:', oname_rest # dbg
1407 1407 for part in oname_rest:
1408 1408 try:
1409 1409 parent = obj
1410 1410 obj = getattr(obj,part)
1411 1411 except:
1412 1412 # Blanket except b/c some badly implemented objects
1413 1413 # allow __getattr__ to raise exceptions other than
1414 1414 # AttributeError, which then crashes IPython.
1415 1415 break
1416 1416 else:
1417 1417 # If we finish the for loop (no break), we got all members
1418 1418 found = True
1419 1419 ospace = nsname
1420 1420 if ns == alias_ns:
1421 1421 isalias = True
1422 1422 break # namespace loop
1423 1423
1424 1424 # Try to see if it's magic
1425 1425 if not found:
1426 1426 obj = None
1427 1427 if oname.startswith(ESC_MAGIC2):
1428 1428 oname = oname.lstrip(ESC_MAGIC2)
1429 1429 obj = self.find_cell_magic(oname)
1430 1430 elif oname.startswith(ESC_MAGIC):
1431 1431 oname = oname.lstrip(ESC_MAGIC)
1432 1432 obj = self.find_line_magic(oname)
1433 1433 else:
1434 1434 # search without prefix, so run? will find %run?
1435 1435 obj = self.find_line_magic(oname)
1436 1436 if obj is None:
1437 1437 obj = self.find_cell_magic(oname)
1438 1438 if obj is not None:
1439 1439 found = True
1440 1440 ospace = 'IPython internal'
1441 1441 ismagic = True
1442 1442
1443 1443 # Last try: special-case some literals like '', [], {}, etc:
1444 1444 if not found and oname_head in ["''",'""','[]','{}','()']:
1445 1445 obj = eval(oname_head)
1446 1446 found = True
1447 1447 ospace = 'Interactive'
1448 1448
1449 1449 return {'found':found, 'obj':obj, 'namespace':ospace,
1450 1450 'ismagic':ismagic, 'isalias':isalias, 'parent':parent}
1451 1451
1452 1452 def _ofind_property(self, oname, info):
1453 1453 """Second part of object finding, to look for property details."""
1454 1454 if info.found:
1455 1455 # Get the docstring of the class property if it exists.
1456 1456 path = oname.split('.')
1457 1457 root = '.'.join(path[:-1])
1458 1458 if info.parent is not None:
1459 1459 try:
1460 1460 target = getattr(info.parent, '__class__')
1461 1461 # The object belongs to a class instance.
1462 1462 try:
1463 1463 target = getattr(target, path[-1])
1464 1464 # The class defines the object.
1465 1465 if isinstance(target, property):
1466 1466 oname = root + '.__class__.' + path[-1]
1467 1467 info = Struct(self._ofind(oname))
1468 1468 except AttributeError: pass
1469 1469 except AttributeError: pass
1470 1470
1471 1471 # We return either the new info or the unmodified input if the object
1472 1472 # hadn't been found
1473 1473 return info
1474 1474
1475 1475 def _object_find(self, oname, namespaces=None):
1476 1476 """Find an object and return a struct with info about it."""
1477 1477 inf = Struct(self._ofind(oname, namespaces))
1478 1478 return Struct(self._ofind_property(oname, inf))
1479 1479
1480 1480 def _inspect(self, meth, oname, namespaces=None, **kw):
1481 1481 """Generic interface to the inspector system.
1482 1482
1483 1483 This function is meant to be called by pdef, pdoc & friends."""
1484 1484 info = self._object_find(oname, namespaces)
1485 1485 if info.found:
1486 1486 pmethod = getattr(self.inspector, meth)
1487 1487 formatter = format_screen if info.ismagic else None
1488 1488 if meth == 'pdoc':
1489 1489 pmethod(info.obj, oname, formatter)
1490 1490 elif meth == 'pinfo':
1491 1491 pmethod(info.obj, oname, formatter, info, **kw)
1492 1492 else:
1493 1493 pmethod(info.obj, oname)
1494 1494 else:
1495 1495 print('Object `%s` not found.' % oname)
1496 1496 return 'not found' # so callers can take other action
1497 1497
1498 1498 def object_inspect(self, oname, detail_level=0):
1499 1499 with self.builtin_trap:
1500 1500 info = self._object_find(oname)
1501 1501 if info.found:
1502 1502 return self.inspector.info(info.obj, oname, info=info,
1503 1503 detail_level=detail_level
1504 1504 )
1505 1505 else:
1506 1506 return oinspect.object_info(name=oname, found=False)
1507 1507
1508 1508 #-------------------------------------------------------------------------
1509 1509 # Things related to history management
1510 1510 #-------------------------------------------------------------------------
1511 1511
1512 1512 def init_history(self):
1513 1513 """Sets up the command history, and starts regular autosaves."""
1514 1514 self.history_manager = HistoryManager(shell=self, config=self.config)
1515 1515 self.configurables.append(self.history_manager)
1516 1516
1517 1517 #-------------------------------------------------------------------------
1518 1518 # Things related to exception handling and tracebacks (not debugging)
1519 1519 #-------------------------------------------------------------------------
1520 1520
1521 1521 def init_traceback_handlers(self, custom_exceptions):
1522 1522 # Syntax error handler.
1523 1523 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
1524 1524
1525 1525 # The interactive one is initialized with an offset, meaning we always
1526 1526 # want to remove the topmost item in the traceback, which is our own
1527 1527 # internal code. Valid modes: ['Plain','Context','Verbose']
1528 1528 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1529 1529 color_scheme='NoColor',
1530 1530 tb_offset = 1,
1531 1531 check_cache=check_linecache_ipython)
1532 1532
1533 1533 # The instance will store a pointer to the system-wide exception hook,
1534 1534 # so that runtime code (such as magics) can access it. This is because
1535 1535 # during the read-eval loop, it may get temporarily overwritten.
1536 1536 self.sys_excepthook = sys.excepthook
1537 1537
1538 1538 # and add any custom exception handlers the user may have specified
1539 1539 self.set_custom_exc(*custom_exceptions)
1540 1540
1541 1541 # Set the exception mode
1542 1542 self.InteractiveTB.set_mode(mode=self.xmode)
1543 1543
1544 1544 def set_custom_exc(self, exc_tuple, handler):
1545 1545 """set_custom_exc(exc_tuple,handler)
1546 1546
1547 1547 Set a custom exception handler, which will be called if any of the
1548 1548 exceptions in exc_tuple occur in the mainloop (specifically, in the
1549 1549 run_code() method).
1550 1550
1551 1551 Parameters
1552 1552 ----------
1553 1553
1554 1554 exc_tuple : tuple of exception classes
1555 1555 A *tuple* of exception classes, for which to call the defined
1556 1556 handler. It is very important that you use a tuple, and NOT A
1557 1557 LIST here, because of the way Python's except statement works. If
1558 1558 you only want to trap a single exception, use a singleton tuple::
1559 1559
1560 1560 exc_tuple == (MyCustomException,)
1561 1561
1562 1562 handler : callable
1563 1563 handler must have the following signature::
1564 1564
1565 1565 def my_handler(self, etype, value, tb, tb_offset=None):
1566 1566 ...
1567 1567 return structured_traceback
1568 1568
1569 1569 Your handler must return a structured traceback (a list of strings),
1570 1570 or None.
1571 1571
1572 1572 This will be made into an instance method (via types.MethodType)
1573 1573 of IPython itself, and it will be called if any of the exceptions
1574 1574 listed in the exc_tuple are caught. If the handler is None, an
1575 1575 internal basic one is used, which just prints basic info.
1576 1576
1577 1577 To protect IPython from crashes, if your handler ever raises an
1578 1578 exception or returns an invalid result, it will be immediately
1579 1579 disabled.
1580 1580
1581 1581 WARNING: by putting in your own exception handler into IPython's main
1582 1582 execution loop, you run a very good chance of nasty crashes. This
1583 1583 facility should only be used if you really know what you are doing."""
1584 1584
1585 1585 assert type(exc_tuple)==type(()) , \
1586 1586 "The custom exceptions must be given AS A TUPLE."
1587 1587
1588 1588 def dummy_handler(self,etype,value,tb,tb_offset=None):
1589 1589 print('*** Simple custom exception handler ***')
1590 1590 print('Exception type :',etype)
1591 1591 print('Exception value:',value)
1592 1592 print('Traceback :',tb)
1593 1593 #print 'Source code :','\n'.join(self.buffer)
1594 1594
1595 1595 def validate_stb(stb):
1596 1596 """validate structured traceback return type
1597 1597
1598 1598 return type of CustomTB *should* be a list of strings, but allow
1599 1599 single strings or None, which are harmless.
1600 1600
1601 1601 This function will *always* return a list of strings,
1602 1602 and will raise a TypeError if stb is inappropriate.
1603 1603 """
1604 1604 msg = "CustomTB must return list of strings, not %r" % stb
1605 1605 if stb is None:
1606 1606 return []
1607 1607 elif isinstance(stb, basestring):
1608 1608 return [stb]
1609 1609 elif not isinstance(stb, list):
1610 1610 raise TypeError(msg)
1611 1611 # it's a list
1612 1612 for line in stb:
1613 1613 # check every element
1614 1614 if not isinstance(line, basestring):
1615 1615 raise TypeError(msg)
1616 1616 return stb
1617 1617
1618 1618 if handler is None:
1619 1619 wrapped = dummy_handler
1620 1620 else:
1621 1621 def wrapped(self,etype,value,tb,tb_offset=None):
1622 1622 """wrap CustomTB handler, to protect IPython from user code
1623 1623
1624 1624 This makes it harder (but not impossible) for custom exception
1625 1625 handlers to crash IPython.
1626 1626 """
1627 1627 try:
1628 1628 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
1629 1629 return validate_stb(stb)
1630 1630 except:
1631 1631 # clear custom handler immediately
1632 1632 self.set_custom_exc((), None)
1633 1633 print("Custom TB Handler failed, unregistering", file=io.stderr)
1634 1634 # show the exception in handler first
1635 1635 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
1636 1636 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1637 1637 print("The original exception:", file=io.stdout)
1638 1638 stb = self.InteractiveTB.structured_traceback(
1639 1639 (etype,value,tb), tb_offset=tb_offset
1640 1640 )
1641 1641 return stb
1642 1642
1643 1643 self.CustomTB = types.MethodType(wrapped,self)
1644 1644 self.custom_exceptions = exc_tuple
1645 1645
1646 1646 def excepthook(self, etype, value, tb):
1647 1647 """One more defense for GUI apps that call sys.excepthook.
1648 1648
1649 1649 GUI frameworks like wxPython trap exceptions and call
1650 1650 sys.excepthook themselves. I guess this is a feature that
1651 1651 enables them to keep running after exceptions that would
1652 1652 otherwise kill their mainloop. This is a bother for IPython
1653 1653 which excepts to catch all of the program exceptions with a try:
1654 1654 except: statement.
1655 1655
1656 1656 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
1657 1657 any app directly invokes sys.excepthook, it will look to the user like
1658 1658 IPython crashed. In order to work around this, we can disable the
1659 1659 CrashHandler and replace it with this excepthook instead, which prints a
1660 1660 regular traceback using our InteractiveTB. In this fashion, apps which
1661 1661 call sys.excepthook will generate a regular-looking exception from
1662 1662 IPython, and the CrashHandler will only be triggered by real IPython
1663 1663 crashes.
1664 1664
1665 1665 This hook should be used sparingly, only in places which are not likely
1666 1666 to be true IPython errors.
1667 1667 """
1668 1668 self.showtraceback((etype,value,tb),tb_offset=0)
1669 1669
1670 1670 def _get_exc_info(self, exc_tuple=None):
1671 1671 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1672 1672
1673 1673 Ensures sys.last_type,value,traceback hold the exc_info we found,
1674 1674 from whichever source.
1675 1675
1676 1676 raises ValueError if none of these contain any information
1677 1677 """
1678 1678 if exc_tuple is None:
1679 1679 etype, value, tb = sys.exc_info()
1680 1680 else:
1681 1681 etype, value, tb = exc_tuple
1682 1682
1683 1683 if etype is None:
1684 1684 if hasattr(sys, 'last_type'):
1685 1685 etype, value, tb = sys.last_type, sys.last_value, \
1686 1686 sys.last_traceback
1687 1687
1688 1688 if etype is None:
1689 1689 raise ValueError("No exception to find")
1690 1690
1691 1691 # Now store the exception info in sys.last_type etc.
1692 1692 # WARNING: these variables are somewhat deprecated and not
1693 1693 # necessarily safe to use in a threaded environment, but tools
1694 1694 # like pdb depend on their existence, so let's set them. If we
1695 1695 # find problems in the field, we'll need to revisit their use.
1696 1696 sys.last_type = etype
1697 1697 sys.last_value = value
1698 1698 sys.last_traceback = tb
1699 1699
1700 1700 return etype, value, tb
1701 1701
1702 1702
1703 1703 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
1704 1704 exception_only=False):
1705 1705 """Display the exception that just occurred.
1706 1706
1707 1707 If nothing is known about the exception, this is the method which
1708 1708 should be used throughout the code for presenting user tracebacks,
1709 1709 rather than directly invoking the InteractiveTB object.
1710 1710
1711 1711 A specific showsyntaxerror() also exists, but this method can take
1712 1712 care of calling it if needed, so unless you are explicitly catching a
1713 1713 SyntaxError exception, don't try to analyze the stack manually and
1714 1714 simply call this method."""
1715 1715
1716 1716 try:
1717 1717 try:
1718 1718 etype, value, tb = self._get_exc_info(exc_tuple)
1719 1719 except ValueError:
1720 1720 self.write_err('No traceback available to show.\n')
1721 1721 return
1722 1722
1723 1723 if issubclass(etype, SyntaxError):
1724 1724 # Though this won't be called by syntax errors in the input
1725 1725 # line, there may be SyntaxError cases with imported code.
1726 1726 self.showsyntaxerror(filename)
1727 1727 elif etype is UsageError:
1728 1728 self.write_err("UsageError: %s" % value)
1729 1729 else:
1730 1730 if exception_only:
1731 1731 stb = ['An exception has occurred, use %tb to see '
1732 1732 'the full traceback.\n']
1733 1733 stb.extend(self.InteractiveTB.get_exception_only(etype,
1734 1734 value))
1735 1735 else:
1736 1736 try:
1737 1737 # Exception classes can customise their traceback - we
1738 1738 # use this in IPython.parallel for exceptions occurring
1739 1739 # in the engines. This should return a list of strings.
1740 1740 stb = value._render_traceback_()
1741 1741 except Exception:
1742 1742 stb = self.InteractiveTB.structured_traceback(etype,
1743 1743 value, tb, tb_offset=tb_offset)
1744 1744
1745 1745 self._showtraceback(etype, value, stb)
1746 1746 if self.call_pdb:
1747 1747 # drop into debugger
1748 1748 self.debugger(force=True)
1749 1749 return
1750 1750
1751 1751 # Actually show the traceback
1752 1752 self._showtraceback(etype, value, stb)
1753 1753
1754 1754 except KeyboardInterrupt:
1755 1755 self.write_err("\nKeyboardInterrupt\n")
1756 1756
1757 1757 def _showtraceback(self, etype, evalue, stb):
1758 1758 """Actually show a traceback.
1759 1759
1760 1760 Subclasses may override this method to put the traceback on a different
1761 1761 place, like a side channel.
1762 1762 """
1763 1763 print(self.InteractiveTB.stb2text(stb), file=io.stdout)
1764 1764
1765 1765 def showsyntaxerror(self, filename=None):
1766 1766 """Display the syntax error that just occurred.
1767 1767
1768 1768 This doesn't display a stack trace because there isn't one.
1769 1769
1770 1770 If a filename is given, it is stuffed in the exception instead
1771 1771 of what was there before (because Python's parser always uses
1772 1772 "<string>" when reading from a string).
1773 1773 """
1774 1774 etype, value, last_traceback = self._get_exc_info()
1775 1775
1776 1776 if filename and issubclass(etype, SyntaxError):
1777 1777 try:
1778 1778 value.filename = filename
1779 1779 except:
1780 1780 # Not the format we expect; leave it alone
1781 1781 pass
1782 1782
1783 1783 stb = self.SyntaxTB.structured_traceback(etype, value, [])
1784 1784 self._showtraceback(etype, value, stb)
1785 1785
1786 1786 # This is overridden in TerminalInteractiveShell to show a message about
1787 1787 # the %paste magic.
1788 1788 def showindentationerror(self):
1789 1789 """Called by run_cell when there's an IndentationError in code entered
1790 1790 at the prompt.
1791 1791
1792 1792 This is overridden in TerminalInteractiveShell to show a message about
1793 1793 the %paste magic."""
1794 1794 self.showsyntaxerror()
1795 1795
1796 1796 #-------------------------------------------------------------------------
1797 1797 # Things related to readline
1798 1798 #-------------------------------------------------------------------------
1799 1799
1800 1800 def init_readline(self):
1801 1801 """Command history completion/saving/reloading."""
1802 1802
1803 1803 if self.readline_use:
1804 1804 import IPython.utils.rlineimpl as readline
1805 1805
1806 1806 self.rl_next_input = None
1807 1807 self.rl_do_indent = False
1808 1808
1809 1809 if not self.readline_use or not readline.have_readline:
1810 1810 self.has_readline = False
1811 1811 self.readline = None
1812 1812 # Set a number of methods that depend on readline to be no-op
1813 1813 self.readline_no_record = no_op_context
1814 1814 self.set_readline_completer = no_op
1815 1815 self.set_custom_completer = no_op
1816 1816 if self.readline_use:
1817 1817 warn('Readline services not available or not loaded.')
1818 1818 else:
1819 1819 self.has_readline = True
1820 1820 self.readline = readline
1821 1821 sys.modules['readline'] = readline
1822 1822
1823 1823 # Platform-specific configuration
1824 1824 if os.name == 'nt':
1825 1825 # FIXME - check with Frederick to see if we can harmonize
1826 1826 # naming conventions with pyreadline to avoid this
1827 1827 # platform-dependent check
1828 1828 self.readline_startup_hook = readline.set_pre_input_hook
1829 1829 else:
1830 1830 self.readline_startup_hook = readline.set_startup_hook
1831 1831
1832 1832 # Load user's initrc file (readline config)
1833 1833 # Or if libedit is used, load editrc.
1834 1834 inputrc_name = os.environ.get('INPUTRC')
1835 1835 if inputrc_name is None:
1836 1836 inputrc_name = '.inputrc'
1837 1837 if readline.uses_libedit:
1838 1838 inputrc_name = '.editrc'
1839 1839 inputrc_name = os.path.join(self.home_dir, inputrc_name)
1840 1840 if os.path.isfile(inputrc_name):
1841 1841 try:
1842 1842 readline.read_init_file(inputrc_name)
1843 1843 except:
1844 1844 warn('Problems reading readline initialization file <%s>'
1845 1845 % inputrc_name)
1846 1846
1847 1847 # Configure readline according to user's prefs
1848 1848 # This is only done if GNU readline is being used. If libedit
1849 1849 # is being used (as on Leopard) the readline config is
1850 1850 # not run as the syntax for libedit is different.
1851 1851 if not readline.uses_libedit:
1852 1852 for rlcommand in self.readline_parse_and_bind:
1853 1853 #print "loading rl:",rlcommand # dbg
1854 1854 readline.parse_and_bind(rlcommand)
1855 1855
1856 1856 # Remove some chars from the delimiters list. If we encounter
1857 1857 # unicode chars, discard them.
1858 1858 delims = readline.get_completer_delims()
1859 1859 if not py3compat.PY3:
1860 1860 delims = delims.encode("ascii", "ignore")
1861 1861 for d in self.readline_remove_delims:
1862 1862 delims = delims.replace(d, "")
1863 1863 delims = delims.replace(ESC_MAGIC, '')
1864 1864 readline.set_completer_delims(delims)
1865 1865 # Store these so we can restore them if something like rpy2 modifies
1866 1866 # them.
1867 1867 self.readline_delims = delims
1868 1868 # otherwise we end up with a monster history after a while:
1869 1869 readline.set_history_length(self.history_length)
1870 1870
1871 1871 self.refill_readline_hist()
1872 1872 self.readline_no_record = ReadlineNoRecord(self)
1873 1873
1874 1874 # Configure auto-indent for all platforms
1875 1875 self.set_autoindent(self.autoindent)
1876 1876
1877 1877 def refill_readline_hist(self):
1878 1878 # Load the last 1000 lines from history
1879 1879 self.readline.clear_history()
1880 1880 stdin_encoding = sys.stdin.encoding or "utf-8"
1881 1881 last_cell = u""
1882 1882 for _, _, cell in self.history_manager.get_tail(1000,
1883 1883 include_latest=True):
1884 1884 # Ignore blank lines and consecutive duplicates
1885 1885 cell = cell.rstrip()
1886 1886 if cell and (cell != last_cell):
1887 1887 if self.multiline_history:
1888 1888 self.readline.add_history(py3compat.unicode_to_str(cell,
1889 1889 stdin_encoding))
1890 1890 else:
1891 1891 for line in cell.splitlines():
1892 1892 self.readline.add_history(py3compat.unicode_to_str(line,
1893 1893 stdin_encoding))
1894 1894 last_cell = cell
1895 1895
1896 1896 @skip_doctest
1897 1897 def set_next_input(self, s):
1898 1898 """ Sets the 'default' input string for the next command line.
1899 1899
1900 1900 Requires readline.
1901 1901
1902 1902 Example::
1903 1903
1904 1904 In [1]: _ip.set_next_input("Hello Word")
1905 1905 In [2]: Hello Word_ # cursor is here
1906 1906 """
1907 1907 self.rl_next_input = py3compat.cast_bytes_py2(s)
1908 1908
1909 1909 # Maybe move this to the terminal subclass?
1910 1910 def pre_readline(self):
1911 1911 """readline hook to be used at the start of each line.
1912 1912
1913 1913 Currently it handles auto-indent only."""
1914 1914
1915 1915 if self.rl_do_indent:
1916 1916 self.readline.insert_text(self._indent_current_str())
1917 1917 if self.rl_next_input is not None:
1918 1918 self.readline.insert_text(self.rl_next_input)
1919 1919 self.rl_next_input = None
1920 1920
1921 1921 def _indent_current_str(self):
1922 1922 """return the current level of indentation as a string"""
1923 1923 return self.input_splitter.indent_spaces * ' '
1924 1924
1925 1925 #-------------------------------------------------------------------------
1926 1926 # Things related to text completion
1927 1927 #-------------------------------------------------------------------------
1928 1928
1929 1929 def init_completer(self):
1930 1930 """Initialize the completion machinery.
1931 1931
1932 1932 This creates completion machinery that can be used by client code,
1933 1933 either interactively in-process (typically triggered by the readline
1934 1934 library), programatically (such as in test suites) or out-of-prcess
1935 1935 (typically over the network by remote frontends).
1936 1936 """
1937 1937 from IPython.core.completer import IPCompleter
1938 1938 from IPython.core.completerlib import (module_completer,
1939 1939 magic_run_completer, cd_completer, reset_completer)
1940 1940
1941 1941 self.Completer = IPCompleter(shell=self,
1942 1942 namespace=self.user_ns,
1943 1943 global_namespace=self.user_global_ns,
1944 1944 alias_table=self.alias_manager.alias_table,
1945 1945 use_readline=self.has_readline,
1946 1946 config=self.config,
1947 1947 )
1948 1948 self.configurables.append(self.Completer)
1949 1949
1950 1950 # Add custom completers to the basic ones built into IPCompleter
1951 1951 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
1952 1952 self.strdispatchers['complete_command'] = sdisp
1953 1953 self.Completer.custom_completers = sdisp
1954 1954
1955 1955 self.set_hook('complete_command', module_completer, str_key = 'import')
1956 1956 self.set_hook('complete_command', module_completer, str_key = 'from')
1957 1957 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
1958 1958 self.set_hook('complete_command', cd_completer, str_key = '%cd')
1959 1959 self.set_hook('complete_command', reset_completer, str_key = '%reset')
1960 1960
1961 1961 # Only configure readline if we truly are using readline. IPython can
1962 1962 # do tab-completion over the network, in GUIs, etc, where readline
1963 1963 # itself may be absent
1964 1964 if self.has_readline:
1965 1965 self.set_readline_completer()
1966 1966
1967 1967 def complete(self, text, line=None, cursor_pos=None):
1968 1968 """Return the completed text and a list of completions.
1969 1969
1970 1970 Parameters
1971 1971 ----------
1972 1972
1973 1973 text : string
1974 1974 A string of text to be completed on. It can be given as empty and
1975 1975 instead a line/position pair are given. In this case, the
1976 1976 completer itself will split the line like readline does.
1977 1977
1978 1978 line : string, optional
1979 1979 The complete line that text is part of.
1980 1980
1981 1981 cursor_pos : int, optional
1982 1982 The position of the cursor on the input line.
1983 1983
1984 1984 Returns
1985 1985 -------
1986 1986 text : string
1987 1987 The actual text that was completed.
1988 1988
1989 1989 matches : list
1990 1990 A sorted list with all possible completions.
1991 1991
1992 1992 The optional arguments allow the completion to take more context into
1993 1993 account, and are part of the low-level completion API.
1994 1994
1995 1995 This is a wrapper around the completion mechanism, similar to what
1996 1996 readline does at the command line when the TAB key is hit. By
1997 1997 exposing it as a method, it can be used by other non-readline
1998 1998 environments (such as GUIs) for text completion.
1999 1999
2000 2000 Simple usage example:
2001 2001
2002 2002 In [1]: x = 'hello'
2003 2003
2004 2004 In [2]: _ip.complete('x.l')
2005 2005 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2006 2006 """
2007 2007
2008 2008 # Inject names into __builtin__ so we can complete on the added names.
2009 2009 with self.builtin_trap:
2010 2010 return self.Completer.complete(text, line, cursor_pos)
2011 2011
2012 2012 def set_custom_completer(self, completer, pos=0):
2013 2013 """Adds a new custom completer function.
2014 2014
2015 2015 The position argument (defaults to 0) is the index in the completers
2016 2016 list where you want the completer to be inserted."""
2017 2017
2018 2018 newcomp = types.MethodType(completer,self.Completer)
2019 2019 self.Completer.matchers.insert(pos,newcomp)
2020 2020
2021 2021 def set_readline_completer(self):
2022 2022 """Reset readline's completer to be our own."""
2023 2023 self.readline.set_completer(self.Completer.rlcomplete)
2024 2024
2025 2025 def set_completer_frame(self, frame=None):
2026 2026 """Set the frame of the completer."""
2027 2027 if frame:
2028 2028 self.Completer.namespace = frame.f_locals
2029 2029 self.Completer.global_namespace = frame.f_globals
2030 2030 else:
2031 2031 self.Completer.namespace = self.user_ns
2032 2032 self.Completer.global_namespace = self.user_global_ns
2033 2033
2034 2034 #-------------------------------------------------------------------------
2035 2035 # Things related to magics
2036 2036 #-------------------------------------------------------------------------
2037 2037
2038 2038 def init_magics(self):
2039 2039 from IPython.core import magics as m
2040 2040 self.magics_manager = magic.MagicsManager(shell=self,
2041 2041 config=self.config,
2042 2042 user_magics=m.UserMagics(self))
2043 2043 self.configurables.append(self.magics_manager)
2044 2044
2045 2045 # Expose as public API from the magics manager
2046 2046 self.register_magics = self.magics_manager.register
2047 2047 self.register_magic_function = self.magics_manager.register_function
2048 2048 self.define_magic = self.magics_manager.define_magic
2049 2049
2050 2050 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2051 2051 m.ConfigMagics, m.DeprecatedMagics, m.DisplayMagics, m.ExecutionMagics,
2052 2052 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2053 2053 m.NamespaceMagics, m.OSMagics, m.PylabMagics, m.ScriptMagics,
2054 2054 )
2055 2055
2056 2056 # Register Magic Aliases
2057 2057 mman = self.magics_manager
2058 2058 mman.register_alias('ed', 'edit')
2059 2059 mman.register_alias('hist', 'history')
2060 2060 mman.register_alias('rep', 'recall')
2061 2061 mman.register_alias('SVG', 'svg', 'cell')
2062 2062 mman.register_alias('HTML', 'html', 'cell')
2063 2063
2064 2064 # FIXME: Move the color initialization to the DisplayHook, which
2065 2065 # should be split into a prompt manager and displayhook. We probably
2066 2066 # even need a centralize colors management object.
2067 2067 self.magic('colors %s' % self.colors)
2068 2068
2069 2069 def run_line_magic(self, magic_name, line):
2070 2070 """Execute the given line magic.
2071 2071
2072 2072 Parameters
2073 2073 ----------
2074 2074 magic_name : str
2075 2075 Name of the desired magic function, without '%' prefix.
2076 2076
2077 2077 line : str
2078 2078 The rest of the input line as a single string.
2079 2079 """
2080 2080 fn = self.find_line_magic(magic_name)
2081 2081 if fn is None:
2082 2082 cm = self.find_cell_magic(magic_name)
2083 2083 etpl = "Line magic function `%%%s` not found%s."
2084 2084 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2085 2085 'did you mean that instead?)' % magic_name )
2086 2086 error(etpl % (magic_name, extra))
2087 2087 else:
2088 2088 # Note: this is the distance in the stack to the user's frame.
2089 2089 # This will need to be updated if the internal calling logic gets
2090 2090 # refactored, or else we'll be expanding the wrong variables.
2091 2091 stack_depth = 2
2092 2092 magic_arg_s = self.var_expand(line, stack_depth)
2093 2093 # Put magic args in a list so we can call with f(*a) syntax
2094 2094 args = [magic_arg_s]
2095 2095 kwargs = {}
2096 2096 # Grab local namespace if we need it:
2097 2097 if getattr(fn, "needs_local_scope", False):
2098 2098 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
2099 2099 with self.builtin_trap:
2100 2100 result = fn(*args,**kwargs)
2101 2101 return result
2102 2102
2103 2103 def run_cell_magic(self, magic_name, line, cell):
2104 2104 """Execute the given cell magic.
2105 2105
2106 2106 Parameters
2107 2107 ----------
2108 2108 magic_name : str
2109 2109 Name of the desired magic function, without '%' prefix.
2110 2110
2111 2111 line : str
2112 2112 The rest of the first input line as a single string.
2113 2113
2114 2114 cell : str
2115 2115 The body of the cell as a (possibly multiline) string.
2116 2116 """
2117 2117 fn = self.find_cell_magic(magic_name)
2118 2118 if fn is None:
2119 2119 lm = self.find_line_magic(magic_name)
2120 2120 etpl = "Cell magic `%%{0}` not found{1}."
2121 2121 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2122 2122 'did you mean that instead?)'.format(magic_name))
2123 2123 error(etpl.format(magic_name, extra))
2124 2124 elif cell == '':
2125 2125 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2126 2126 if self.find_line_magic(magic_name) is not None:
2127 2127 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2128 2128 raise UsageError(message)
2129 2129 else:
2130 2130 # Note: this is the distance in the stack to the user's frame.
2131 2131 # This will need to be updated if the internal calling logic gets
2132 2132 # refactored, or else we'll be expanding the wrong variables.
2133 2133 stack_depth = 2
2134 2134 magic_arg_s = self.var_expand(line, stack_depth)
2135 2135 with self.builtin_trap:
2136 2136 result = fn(magic_arg_s, cell)
2137 2137 return result
2138 2138
2139 2139 def find_line_magic(self, magic_name):
2140 2140 """Find and return a line magic by name.
2141 2141
2142 2142 Returns None if the magic isn't found."""
2143 2143 return self.magics_manager.magics['line'].get(magic_name)
2144 2144
2145 2145 def find_cell_magic(self, magic_name):
2146 2146 """Find and return a cell magic by name.
2147 2147
2148 2148 Returns None if the magic isn't found."""
2149 2149 return self.magics_manager.magics['cell'].get(magic_name)
2150 2150
2151 2151 def find_magic(self, magic_name, magic_kind='line'):
2152 2152 """Find and return a magic of the given type by name.
2153 2153
2154 2154 Returns None if the magic isn't found."""
2155 2155 return self.magics_manager.magics[magic_kind].get(magic_name)
2156 2156
2157 2157 def magic(self, arg_s):
2158 2158 """DEPRECATED. Use run_line_magic() instead.
2159 2159
2160 2160 Call a magic function by name.
2161 2161
2162 2162 Input: a string containing the name of the magic function to call and
2163 2163 any additional arguments to be passed to the magic.
2164 2164
2165 2165 magic('name -opt foo bar') is equivalent to typing at the ipython
2166 2166 prompt:
2167 2167
2168 2168 In[1]: %name -opt foo bar
2169 2169
2170 2170 To call a magic without arguments, simply use magic('name').
2171 2171
2172 2172 This provides a proper Python function to call IPython's magics in any
2173 2173 valid Python code you can type at the interpreter, including loops and
2174 2174 compound statements.
2175 2175 """
2176 2176 # TODO: should we issue a loud deprecation warning here?
2177 2177 magic_name, _, magic_arg_s = arg_s.partition(' ')
2178 2178 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2179 2179 return self.run_line_magic(magic_name, magic_arg_s)
2180 2180
2181 2181 #-------------------------------------------------------------------------
2182 2182 # Things related to macros
2183 2183 #-------------------------------------------------------------------------
2184 2184
2185 2185 def define_macro(self, name, themacro):
2186 2186 """Define a new macro
2187 2187
2188 2188 Parameters
2189 2189 ----------
2190 2190 name : str
2191 2191 The name of the macro.
2192 2192 themacro : str or Macro
2193 2193 The action to do upon invoking the macro. If a string, a new
2194 2194 Macro object is created by passing the string to it.
2195 2195 """
2196 2196
2197 2197 from IPython.core import macro
2198 2198
2199 2199 if isinstance(themacro, basestring):
2200 2200 themacro = macro.Macro(themacro)
2201 2201 if not isinstance(themacro, macro.Macro):
2202 2202 raise ValueError('A macro must be a string or a Macro instance.')
2203 2203 self.user_ns[name] = themacro
2204 2204
2205 2205 #-------------------------------------------------------------------------
2206 2206 # Things related to the running of system commands
2207 2207 #-------------------------------------------------------------------------
2208 2208
2209 2209 def system_piped(self, cmd):
2210 2210 """Call the given cmd in a subprocess, piping stdout/err
2211 2211
2212 2212 Parameters
2213 2213 ----------
2214 2214 cmd : str
2215 2215 Command to execute (can not end in '&', as background processes are
2216 2216 not supported. Should not be a command that expects input
2217 2217 other than simple text.
2218 2218 """
2219 2219 if cmd.rstrip().endswith('&'):
2220 2220 # this is *far* from a rigorous test
2221 2221 # We do not support backgrounding processes because we either use
2222 2222 # pexpect or pipes to read from. Users can always just call
2223 2223 # os.system() or use ip.system=ip.system_raw
2224 2224 # if they really want a background process.
2225 2225 raise OSError("Background processes not supported.")
2226 2226
2227 2227 # we explicitly do NOT return the subprocess status code, because
2228 2228 # a non-None value would trigger :func:`sys.displayhook` calls.
2229 2229 # Instead, we store the exit_code in user_ns.
2230 2230 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2231 2231
2232 2232 def system_raw(self, cmd):
2233 2233 """Call the given cmd in a subprocess using os.system
2234 2234
2235 2235 Parameters
2236 2236 ----------
2237 2237 cmd : str
2238 2238 Command to execute.
2239 2239 """
2240 2240 cmd = self.var_expand(cmd, depth=1)
2241 2241 # protect os.system from UNC paths on Windows, which it can't handle:
2242 2242 if sys.platform == 'win32':
2243 2243 from IPython.utils._process_win32 import AvoidUNCPath
2244 2244 with AvoidUNCPath() as path:
2245 2245 if path is not None:
2246 2246 cmd = '"pushd %s &&"%s' % (path, cmd)
2247 2247 cmd = py3compat.unicode_to_str(cmd)
2248 2248 ec = os.system(cmd)
2249 2249 else:
2250 2250 cmd = py3compat.unicode_to_str(cmd)
2251 2251 ec = os.system(cmd)
2252 2252 # The high byte is the exit code, the low byte is a signal number
2253 2253 # that we discard for now. See the docs for os.wait()
2254 2254 if ec > 255:
2255 2255 ec >>= 8
2256 2256
2257 2257 # We explicitly do NOT return the subprocess status code, because
2258 2258 # a non-None value would trigger :func:`sys.displayhook` calls.
2259 2259 # Instead, we store the exit_code in user_ns.
2260 2260 self.user_ns['_exit_code'] = ec
2261 2261
2262 2262 # use piped system by default, because it is better behaved
2263 2263 system = system_piped
2264 2264
2265 2265 def getoutput(self, cmd, split=True, depth=0):
2266 2266 """Get output (possibly including stderr) from a subprocess.
2267 2267
2268 2268 Parameters
2269 2269 ----------
2270 2270 cmd : str
2271 2271 Command to execute (can not end in '&', as background processes are
2272 2272 not supported.
2273 2273 split : bool, optional
2274 2274 If True, split the output into an IPython SList. Otherwise, an
2275 2275 IPython LSString is returned. These are objects similar to normal
2276 2276 lists and strings, with a few convenience attributes for easier
2277 2277 manipulation of line-based output. You can use '?' on them for
2278 2278 details.
2279 2279 depth : int, optional
2280 2280 How many frames above the caller are the local variables which should
2281 2281 be expanded in the command string? The default (0) assumes that the
2282 2282 expansion variables are in the stack frame calling this function.
2283 2283 """
2284 2284 if cmd.rstrip().endswith('&'):
2285 2285 # this is *far* from a rigorous test
2286 2286 raise OSError("Background processes not supported.")
2287 2287 out = getoutput(self.var_expand(cmd, depth=depth+1))
2288 2288 if split:
2289 2289 out = SList(out.splitlines())
2290 2290 else:
2291 2291 out = LSString(out)
2292 2292 return out
2293 2293
2294 2294 #-------------------------------------------------------------------------
2295 2295 # Things related to aliases
2296 2296 #-------------------------------------------------------------------------
2297 2297
2298 2298 def init_alias(self):
2299 2299 self.alias_manager = AliasManager(shell=self, config=self.config)
2300 2300 self.configurables.append(self.alias_manager)
2301 2301 self.ns_table['alias'] = self.alias_manager.alias_table,
2302 2302
2303 2303 #-------------------------------------------------------------------------
2304 2304 # Things related to extensions
2305 2305 #-------------------------------------------------------------------------
2306 2306
2307 2307 def init_extension_manager(self):
2308 2308 self.extension_manager = ExtensionManager(shell=self, config=self.config)
2309 2309 self.configurables.append(self.extension_manager)
2310 2310
2311 2311 #-------------------------------------------------------------------------
2312 2312 # Things related to payloads
2313 2313 #-------------------------------------------------------------------------
2314 2314
2315 2315 def init_payload(self):
2316 2316 self.payload_manager = PayloadManager(config=self.config)
2317 2317 self.configurables.append(self.payload_manager)
2318 2318
2319 2319 #-------------------------------------------------------------------------
2320 2320 # Things related to the prefilter
2321 2321 #-------------------------------------------------------------------------
2322 2322
2323 2323 def init_prefilter(self):
2324 2324 self.prefilter_manager = PrefilterManager(shell=self, config=self.config)
2325 2325 self.configurables.append(self.prefilter_manager)
2326 2326 # Ultimately this will be refactored in the new interpreter code, but
2327 2327 # for now, we should expose the main prefilter method (there's legacy
2328 2328 # code out there that may rely on this).
2329 2329 self.prefilter = self.prefilter_manager.prefilter_lines
2330 2330
2331 2331 def auto_rewrite_input(self, cmd):
2332 2332 """Print to the screen the rewritten form of the user's command.
2333 2333
2334 2334 This shows visual feedback by rewriting input lines that cause
2335 2335 automatic calling to kick in, like::
2336 2336
2337 2337 /f x
2338 2338
2339 2339 into::
2340 2340
2341 2341 ------> f(x)
2342 2342
2343 2343 after the user's input prompt. This helps the user understand that the
2344 2344 input line was transformed automatically by IPython.
2345 2345 """
2346 2346 if not self.show_rewritten_input:
2347 2347 return
2348 2348
2349 2349 rw = self.prompt_manager.render('rewrite') + cmd
2350 2350
2351 2351 try:
2352 2352 # plain ascii works better w/ pyreadline, on some machines, so
2353 2353 # we use it and only print uncolored rewrite if we have unicode
2354 2354 rw = str(rw)
2355 2355 print(rw, file=io.stdout)
2356 2356 except UnicodeEncodeError:
2357 2357 print("------> " + cmd)
2358 2358
2359 2359 #-------------------------------------------------------------------------
2360 2360 # Things related to extracting values/expressions from kernel and user_ns
2361 2361 #-------------------------------------------------------------------------
2362 2362
2363 2363 def _simple_error(self):
2364 2364 etype, value = sys.exc_info()[:2]
2365 2365 return u'[ERROR] {e.__name__}: {v}'.format(e=etype, v=value)
2366 2366
2367 2367 def user_variables(self, names):
2368 2368 """Get a list of variable names from the user's namespace.
2369 2369
2370 2370 Parameters
2371 2371 ----------
2372 2372 names : list of strings
2373 2373 A list of names of variables to be read from the user namespace.
2374 2374
2375 2375 Returns
2376 2376 -------
2377 2377 A dict, keyed by the input names and with the repr() of each value.
2378 2378 """
2379 2379 out = {}
2380 2380 user_ns = self.user_ns
2381 2381 for varname in names:
2382 2382 try:
2383 2383 value = repr(user_ns[varname])
2384 2384 except:
2385 2385 value = self._simple_error()
2386 2386 out[varname] = value
2387 2387 return out
2388 2388
2389 2389 def user_expressions(self, expressions):
2390 2390 """Evaluate a dict of expressions in the user's namespace.
2391 2391
2392 2392 Parameters
2393 2393 ----------
2394 2394 expressions : dict
2395 2395 A dict with string keys and string values. The expression values
2396 2396 should be valid Python expressions, each of which will be evaluated
2397 2397 in the user namespace.
2398 2398
2399 2399 Returns
2400 2400 -------
2401 2401 A dict, keyed like the input expressions dict, with the repr() of each
2402 2402 value.
2403 2403 """
2404 2404 out = {}
2405 2405 user_ns = self.user_ns
2406 2406 global_ns = self.user_global_ns
2407 2407 for key, expr in expressions.iteritems():
2408 2408 try:
2409 2409 value = repr(eval(expr, global_ns, user_ns))
2410 2410 except:
2411 2411 value = self._simple_error()
2412 2412 out[key] = value
2413 2413 return out
2414 2414
2415 2415 #-------------------------------------------------------------------------
2416 2416 # Things related to the running of code
2417 2417 #-------------------------------------------------------------------------
2418 2418
2419 2419 def ex(self, cmd):
2420 2420 """Execute a normal python statement in user namespace."""
2421 2421 with self.builtin_trap:
2422 2422 exec cmd in self.user_global_ns, self.user_ns
2423 2423
2424 2424 def ev(self, expr):
2425 2425 """Evaluate python expression expr in user namespace.
2426 2426
2427 2427 Returns the result of evaluation
2428 2428 """
2429 2429 with self.builtin_trap:
2430 2430 return eval(expr, self.user_global_ns, self.user_ns)
2431 2431
2432 2432 def safe_execfile(self, fname, *where, **kw):
2433 2433 """A safe version of the builtin execfile().
2434 2434
2435 2435 This version will never throw an exception, but instead print
2436 2436 helpful error messages to the screen. This only works on pure
2437 2437 Python files with the .py extension.
2438 2438
2439 2439 Parameters
2440 2440 ----------
2441 2441 fname : string
2442 2442 The name of the file to be executed.
2443 2443 where : tuple
2444 2444 One or two namespaces, passed to execfile() as (globals,locals).
2445 2445 If only one is given, it is passed as both.
2446 2446 exit_ignore : bool (False)
2447 2447 If True, then silence SystemExit for non-zero status (it is always
2448 2448 silenced for zero status, as it is so common).
2449 2449 raise_exceptions : bool (False)
2450 2450 If True raise exceptions everywhere. Meant for testing.
2451 2451
2452 2452 """
2453 2453 kw.setdefault('exit_ignore', False)
2454 2454 kw.setdefault('raise_exceptions', False)
2455 2455
2456 2456 fname = os.path.abspath(os.path.expanduser(fname))
2457 2457
2458 2458 # Make sure we can open the file
2459 2459 try:
2460 2460 with open(fname) as thefile:
2461 2461 pass
2462 2462 except:
2463 2463 warn('Could not open file <%s> for safe execution.' % fname)
2464 2464 return
2465 2465
2466 2466 # Find things also in current directory. This is needed to mimic the
2467 2467 # behavior of running a script from the system command line, where
2468 2468 # Python inserts the script's directory into sys.path
2469 2469 dname = os.path.dirname(fname)
2470 2470
2471 2471 with prepended_to_syspath(dname):
2472 2472 try:
2473 2473 py3compat.execfile(fname,*where)
2474 2474 except SystemExit as status:
2475 2475 # If the call was made with 0 or None exit status (sys.exit(0)
2476 2476 # or sys.exit() ), don't bother showing a traceback, as both of
2477 2477 # these are considered normal by the OS:
2478 2478 # > python -c'import sys;sys.exit(0)'; echo $?
2479 2479 # 0
2480 2480 # > python -c'import sys;sys.exit()'; echo $?
2481 2481 # 0
2482 2482 # For other exit status, we show the exception unless
2483 2483 # explicitly silenced, but only in short form.
2484 2484 if kw['raise_exceptions']:
2485 2485 raise
2486 2486 if status.code and not kw['exit_ignore']:
2487 2487 self.showtraceback(exception_only=True)
2488 2488 except:
2489 2489 if kw['raise_exceptions']:
2490 2490 raise
2491 2491 self.showtraceback()
2492 2492
2493 2493 def safe_execfile_ipy(self, fname):
2494 2494 """Like safe_execfile, but for .ipy files with IPython syntax.
2495 2495
2496 2496 Parameters
2497 2497 ----------
2498 2498 fname : str
2499 2499 The name of the file to execute. The filename must have a
2500 2500 .ipy extension.
2501 2501 """
2502 2502 fname = os.path.abspath(os.path.expanduser(fname))
2503 2503
2504 2504 # Make sure we can open the file
2505 2505 try:
2506 2506 with open(fname) as thefile:
2507 2507 pass
2508 2508 except:
2509 2509 warn('Could not open file <%s> for safe execution.' % fname)
2510 2510 return
2511 2511
2512 2512 # Find things also in current directory. This is needed to mimic the
2513 2513 # behavior of running a script from the system command line, where
2514 2514 # Python inserts the script's directory into sys.path
2515 2515 dname = os.path.dirname(fname)
2516 2516
2517 2517 with prepended_to_syspath(dname):
2518 2518 try:
2519 2519 with open(fname) as thefile:
2520 2520 # self.run_cell currently captures all exceptions
2521 2521 # raised in user code. It would be nice if there were
2522 2522 # versions of runlines, execfile that did raise, so
2523 2523 # we could catch the errors.
2524 2524 self.run_cell(thefile.read(), store_history=False, shell_futures=False)
2525 2525 except:
2526 2526 self.showtraceback()
2527 2527 warn('Unknown failure executing file: <%s>' % fname)
2528 2528
2529 2529 def safe_run_module(self, mod_name, where):
2530 2530 """A safe version of runpy.run_module().
2531 2531
2532 2532 This version will never throw an exception, but instead print
2533 2533 helpful error messages to the screen.
2534 2534
2535 2535 `SystemExit` exceptions with status code 0 or None are ignored.
2536 2536
2537 2537 Parameters
2538 2538 ----------
2539 2539 mod_name : string
2540 2540 The name of the module to be executed.
2541 2541 where : dict
2542 2542 The globals namespace.
2543 2543 """
2544 2544 try:
2545 2545 try:
2546 2546 where.update(
2547 2547 runpy.run_module(str(mod_name), run_name="__main__",
2548 2548 alter_sys=True)
2549 2549 )
2550 2550 except SystemExit as status:
2551 2551 if status.code:
2552 2552 raise
2553 2553 except:
2554 2554 self.showtraceback()
2555 2555 warn('Unknown failure executing module: <%s>' % mod_name)
2556 2556
2557 2557 def _run_cached_cell_magic(self, magic_name, line):
2558 2558 """Special method to call a cell magic with the data stored in self.
2559 2559 """
2560 2560 cell = self._current_cell_magic_body
2561 2561 self._current_cell_magic_body = None
2562 2562 return self.run_cell_magic(magic_name, line, cell)
2563 2563
2564 2564 def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=True):
2565 2565 """Run a complete IPython cell.
2566 2566
2567 2567 Parameters
2568 2568 ----------
2569 2569 raw_cell : str
2570 2570 The code (including IPython code such as %magic functions) to run.
2571 2571 store_history : bool
2572 2572 If True, the raw and translated cell will be stored in IPython's
2573 2573 history. For user code calling back into IPython's machinery, this
2574 2574 should be set to False.
2575 2575 silent : bool
2576 2576 If True, avoid side-effects, such as implicit displayhooks and
2577 2577 and logging. silent=True forces store_history=False.
2578 2578 shell_futures : bool
2579 2579 If True, the code will share future statements with the interactive
2580 2580 shell. It will both be affected by previous __future__ imports, and
2581 2581 any __future__ imports in the code will affect the shell. If False,
2582 2582 __future__ imports are not shared in either direction.
2583 2583 """
2584 2584 if (not raw_cell) or raw_cell.isspace():
2585 2585 return
2586 2586
2587 2587 if silent:
2588 2588 store_history = False
2589 2589
2590 2590 self.input_transformer_manager.push(raw_cell)
2591 2591 cell = self.input_transformer_manager.source_reset()
2592 2592
2593 2593 # Our own compiler remembers the __future__ environment. If we want to
2594 2594 # run code with a separate __future__ environment, use the default
2595 2595 # compiler
2596 2596 compiler = self.compile if shell_futures else CachingCompiler()
2597 2597
2598 2598 with self.builtin_trap:
2599 2599 prefilter_failed = False
2600 2600 if len(cell.splitlines()) == 1:
2601 2601 try:
2602 2602 # use prefilter_lines to handle trailing newlines
2603 2603 # restore trailing newline for ast.parse
2604 2604 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
2605 2605 except AliasError as e:
2606 2606 error(e)
2607 2607 prefilter_failed = True
2608 2608 except Exception:
2609 2609 # don't allow prefilter errors to crash IPython
2610 2610 self.showtraceback()
2611 2611 prefilter_failed = True
2612 2612
2613 2613 # Store raw and processed history
2614 2614 if store_history:
2615 2615 self.history_manager.store_inputs(self.execution_count,
2616 2616 cell, raw_cell)
2617 2617 if not silent:
2618 2618 self.logger.log(cell, raw_cell)
2619 2619
2620 2620 if not prefilter_failed:
2621 2621 # don't run if prefilter failed
2622 2622 cell_name = self.compile.cache(cell, self.execution_count)
2623 2623
2624 2624 with self.display_trap:
2625 2625 try:
2626 2626 code_ast = compiler.ast_parse(cell, filename=cell_name)
2627 2627 except IndentationError:
2628 2628 self.showindentationerror()
2629 2629 if store_history:
2630 2630 self.execution_count += 1
2631 2631 return None
2632 2632 except (OverflowError, SyntaxError, ValueError, TypeError,
2633 2633 MemoryError):
2634 2634 self.showsyntaxerror()
2635 2635 if store_history:
2636 2636 self.execution_count += 1
2637 2637 return None
2638 2638
2639 2639 code_ast = self.transform_ast(code_ast)
2640 2640
2641 2641 interactivity = "none" if silent else self.ast_node_interactivity
2642 2642 self.run_ast_nodes(code_ast.body, cell_name,
2643 2643 interactivity=interactivity, compiler=compiler)
2644 2644
2645 2645 # Execute any registered post-execution functions.
2646 2646 # unless we are silent
2647 2647 post_exec = [] if silent else self._post_execute.iteritems()
2648 2648
2649 2649 for func, status in post_exec:
2650 2650 if self.disable_failing_post_execute and not status:
2651 2651 continue
2652 2652 try:
2653 2653 func()
2654 2654 except KeyboardInterrupt:
2655 2655 print("\nKeyboardInterrupt", file=io.stderr)
2656 2656 except Exception:
2657 2657 # register as failing:
2658 2658 self._post_execute[func] = False
2659 2659 self.showtraceback()
2660 2660 print('\n'.join([
2661 2661 "post-execution function %r produced an error." % func,
2662 2662 "If this problem persists, you can disable failing post-exec functions with:",
2663 2663 "",
2664 2664 " get_ipython().disable_failing_post_execute = True"
2665 2665 ]), file=io.stderr)
2666 2666
2667 2667 if store_history:
2668 2668 # Write output to the database. Does nothing unless
2669 2669 # history output logging is enabled.
2670 2670 self.history_manager.store_output(self.execution_count)
2671 2671 # Each cell is a *single* input, regardless of how many lines it has
2672 2672 self.execution_count += 1
2673 2673
2674 2674 def transform_ast(self, node):
2675 2675 """Apply the AST transformations from self.ast_transformers
2676 2676
2677 2677 Parameters
2678 2678 ----------
2679 2679 node : ast.Node
2680 2680 The root node to be transformed. Typically called with the ast.Module
2681 2681 produced by parsing user input.
2682 2682
2683 2683 Returns
2684 2684 -------
2685 2685 An ast.Node corresponding to the node it was called with. Note that it
2686 2686 may also modify the passed object, so don't rely on references to the
2687 2687 original AST.
2688 2688 """
2689 2689 for transformer in self.ast_transformers:
2690 2690 try:
2691 2691 node = transformer.visit(node)
2692 2692 except Exception:
2693 2693 warn("AST transformer %r threw an error. It will be unregistered." % transformer)
2694 2694 self.ast_transformers.remove(transformer)
2695 2695
2696 2696 if self.ast_transformers:
2697 2697 ast.fix_missing_locations(node)
2698 2698 return node
2699 2699
2700 2700
2701 2701 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
2702 2702 compiler=compile):
2703 2703 """Run a sequence of AST nodes. The execution mode depends on the
2704 2704 interactivity parameter.
2705 2705
2706 2706 Parameters
2707 2707 ----------
2708 2708 nodelist : list
2709 2709 A sequence of AST nodes to run.
2710 2710 cell_name : str
2711 2711 Will be passed to the compiler as the filename of the cell. Typically
2712 2712 the value returned by ip.compile.cache(cell).
2713 2713 interactivity : str
2714 2714 'all', 'last', 'last_expr' or 'none', specifying which nodes should be
2715 2715 run interactively (displaying output from expressions). 'last_expr'
2716 2716 will run the last node interactively only if it is an expression (i.e.
2717 2717 expressions in loops or other blocks are not displayed. Other values
2718 2718 for this parameter will raise a ValueError.
2719 2719 compiler : callable
2720 2720 A function with the same interface as the built-in compile(), to turn
2721 2721 the AST nodes into code objects. Default is the built-in compile().
2722 2722 """
2723 2723 if not nodelist:
2724 2724 return
2725 2725
2726 2726 if interactivity == 'last_expr':
2727 2727 if isinstance(nodelist[-1], ast.Expr):
2728 2728 interactivity = "last"
2729 2729 else:
2730 2730 interactivity = "none"
2731 2731
2732 2732 if interactivity == 'none':
2733 2733 to_run_exec, to_run_interactive = nodelist, []
2734 2734 elif interactivity == 'last':
2735 2735 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
2736 2736 elif interactivity == 'all':
2737 2737 to_run_exec, to_run_interactive = [], nodelist
2738 2738 else:
2739 2739 raise ValueError("Interactivity was %r" % interactivity)
2740 2740
2741 2741 exec_count = self.execution_count
2742 2742
2743 2743 try:
2744 2744 for i, node in enumerate(to_run_exec):
2745 2745 mod = ast.Module([node])
2746 2746 code = compiler(mod, cell_name, "exec")
2747 2747 if self.run_code(code):
2748 2748 return True
2749 2749
2750 2750 for i, node in enumerate(to_run_interactive):
2751 2751 mod = ast.Interactive([node])
2752 2752 code = compiler(mod, cell_name, "single")
2753 2753 if self.run_code(code):
2754 2754 return True
2755 2755
2756 2756 # Flush softspace
2757 2757 if softspace(sys.stdout, 0):
2758 2758 print()
2759 2759
2760 2760 except:
2761 2761 # It's possible to have exceptions raised here, typically by
2762 2762 # compilation of odd code (such as a naked 'return' outside a
2763 2763 # function) that did parse but isn't valid. Typically the exception
2764 2764 # is a SyntaxError, but it's safest just to catch anything and show
2765 2765 # the user a traceback.
2766 2766
2767 2767 # We do only one try/except outside the loop to minimize the impact
2768 2768 # on runtime, and also because if any node in the node list is
2769 2769 # broken, we should stop execution completely.
2770 2770 self.showtraceback()
2771 2771
2772 2772 return False
2773 2773
2774 2774 def run_code(self, code_obj):
2775 2775 """Execute a code object.
2776 2776
2777 2777 When an exception occurs, self.showtraceback() is called to display a
2778 2778 traceback.
2779 2779
2780 2780 Parameters
2781 2781 ----------
2782 2782 code_obj : code object
2783 2783 A compiled code object, to be executed
2784 2784
2785 2785 Returns
2786 2786 -------
2787 2787 False : successful execution.
2788 2788 True : an error occurred.
2789 2789 """
2790 2790
2791 2791 # Set our own excepthook in case the user code tries to call it
2792 2792 # directly, so that the IPython crash handler doesn't get triggered
2793 2793 old_excepthook,sys.excepthook = sys.excepthook, self.excepthook
2794 2794
2795 2795 # we save the original sys.excepthook in the instance, in case config
2796 2796 # code (such as magics) needs access to it.
2797 2797 self.sys_excepthook = old_excepthook
2798 2798 outflag = 1 # happens in more places, so it's easier as default
2799 2799 try:
2800 2800 try:
2801 2801 self.hooks.pre_run_code_hook()
2802 2802 #rprint('Running code', repr(code_obj)) # dbg
2803 2803 exec code_obj in self.user_global_ns, self.user_ns
2804 2804 finally:
2805 2805 # Reset our crash handler in place
2806 2806 sys.excepthook = old_excepthook
2807 2807 except SystemExit:
2808 2808 self.showtraceback(exception_only=True)
2809 2809 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2810 2810 except self.custom_exceptions:
2811 2811 etype,value,tb = sys.exc_info()
2812 2812 self.CustomTB(etype,value,tb)
2813 2813 except:
2814 2814 self.showtraceback()
2815 2815 else:
2816 2816 outflag = 0
2817 2817 return outflag
2818 2818
2819 2819 # For backwards compatibility
2820 2820 runcode = run_code
2821 2821
2822 2822 #-------------------------------------------------------------------------
2823 2823 # Things related to GUI support and pylab
2824 2824 #-------------------------------------------------------------------------
2825 2825
2826 2826 def enable_gui(self, gui=None):
2827 2827 raise NotImplementedError('Implement enable_gui in a subclass')
2828 2828
2829 2829 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
2830 2830 """Activate pylab support at runtime.
2831 2831
2832 2832 This turns on support for matplotlib, preloads into the interactive
2833 2833 namespace all of numpy and pylab, and configures IPython to correctly
2834 2834 interact with the GUI event loop. The GUI backend to be used can be
2835 2835 optionally selected with the optional ``gui`` argument.
2836 2836
2837 2837 Parameters
2838 2838 ----------
2839 2839 gui : optional, string
2840 2840 If given, dictates the choice of matplotlib GUI backend to use
2841 2841 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
2842 2842 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
2843 2843 matplotlib (as dictated by the matplotlib build-time options plus the
2844 2844 user's matplotlibrc configuration file). Note that not all backends
2845 2845 make sense in all contexts, for example a terminal ipython can't
2846 2846 display figures inline.
2847 2847 """
2848 2848 from IPython.core.pylabtools import mpl_runner
2849 2849 # We want to prevent the loading of pylab to pollute the user's
2850 2850 # namespace as shown by the %who* magics, so we execute the activation
2851 2851 # code in an empty namespace, and we update *both* user_ns and
2852 2852 # user_ns_hidden with this information.
2853 2853 ns = {}
2854 2854 try:
2855 2855 gui = pylab_activate(ns, gui, import_all, self, welcome_message=welcome_message)
2856 2856 except KeyError:
2857 2857 error("Backend %r not supported" % gui)
2858 2858 return
2859 2859 except ImportError:
2860 2860 error("pylab mode doesn't work as matplotlib could not be found." + \
2861 2861 "\nIs it installed on the system?")
2862 2862 return
2863 2863 self.user_ns.update(ns)
2864 2864 self.user_ns_hidden.update(ns)
2865 2865 # Now we must activate the gui pylab wants to use, and fix %run to take
2866 2866 # plot updates into account
2867 2867 self.enable_gui(gui)
2868 2868 self.magics_manager.registry['ExecutionMagics'].default_runner = \
2869 2869 mpl_runner(self.safe_execfile)
2870 2870
2871 2871 #-------------------------------------------------------------------------
2872 2872 # Utilities
2873 2873 #-------------------------------------------------------------------------
2874 2874
2875 2875 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
2876 2876 """Expand python variables in a string.
2877 2877
2878 2878 The depth argument indicates how many frames above the caller should
2879 2879 be walked to look for the local namespace where to expand variables.
2880 2880
2881 2881 The global namespace for expansion is always the user's interactive
2882 2882 namespace.
2883 2883 """
2884 2884 ns = self.user_ns.copy()
2885 2885 ns.update(sys._getframe(depth+1).f_locals)
2886 2886 try:
2887 2887 # We have to use .vformat() here, because 'self' is a valid and common
2888 2888 # name, and expanding **ns for .format() would make it collide with
2889 2889 # the 'self' argument of the method.
2890 2890 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
2891 2891 except Exception:
2892 2892 # if formatter couldn't format, just let it go untransformed
2893 2893 pass
2894 2894 return cmd
2895 2895
2896 2896 def mktempfile(self, data=None, prefix='ipython_edit_'):
2897 2897 """Make a new tempfile and return its filename.
2898 2898
2899 2899 This makes a call to tempfile.mktemp, but it registers the created
2900 2900 filename internally so ipython cleans it up at exit time.
2901 2901
2902 2902 Optional inputs:
2903 2903
2904 2904 - data(None): if data is given, it gets written out to the temp file
2905 2905 immediately, and the file is closed again."""
2906 2906
2907 2907 filename = tempfile.mktemp('.py', prefix)
2908 2908 self.tempfiles.append(filename)
2909 2909
2910 2910 if data:
2911 2911 tmp_file = open(filename,'w')
2912 2912 tmp_file.write(data)
2913 2913 tmp_file.close()
2914 2914 return filename
2915 2915
2916 2916 # TODO: This should be removed when Term is refactored.
2917 2917 def write(self,data):
2918 2918 """Write a string to the default output"""
2919 2919 io.stdout.write(data)
2920 2920
2921 2921 # TODO: This should be removed when Term is refactored.
2922 2922 def write_err(self,data):
2923 2923 """Write a string to the default error output"""
2924 2924 io.stderr.write(data)
2925 2925
2926 2926 def ask_yes_no(self, prompt, default=None):
2927 2927 if self.quiet:
2928 2928 return True
2929 2929 return ask_yes_no(prompt,default)
2930 2930
2931 2931 def show_usage(self):
2932 2932 """Show a usage message"""
2933 2933 page.page(IPython.core.usage.interactive_usage)
2934 2934
2935 2935 def extract_input_lines(self, range_str, raw=False):
2936 2936 """Return as a string a set of input history slices.
2937 2937
2938 2938 Parameters
2939 2939 ----------
2940 2940 range_str : string
2941 2941 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
2942 2942 since this function is for use by magic functions which get their
2943 2943 arguments as strings. The number before the / is the session
2944 2944 number: ~n goes n back from the current session.
2945 2945
2946 2946 Optional Parameters:
2947 2947 - raw(False): by default, the processed input is used. If this is
2948 2948 true, the raw input history is used instead.
2949 2949
2950 2950 Note that slices can be called with two notations:
2951 2951
2952 2952 N:M -> standard python form, means including items N...(M-1).
2953 2953
2954 2954 N-M -> include items N..M (closed endpoint)."""
2955 2955 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
2956 2956 return "\n".join(x for _, _, x in lines)
2957 2957
2958 2958 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True):
2959 2959 """Get a code string from history, file, url, or a string or macro.
2960 2960
2961 2961 This is mainly used by magic functions.
2962 2962
2963 2963 Parameters
2964 2964 ----------
2965 2965
2966 2966 target : str
2967 2967
2968 2968 A string specifying code to retrieve. This will be tried respectively
2969 2969 as: ranges of input history (see %history for syntax), url,
2970 2970 correspnding .py file, filename, or an expression evaluating to a
2971 2971 string or Macro in the user namespace.
2972 2972
2973 2973 raw : bool
2974 2974 If true (default), retrieve raw history. Has no effect on the other
2975 2975 retrieval mechanisms.
2976 2976
2977 2977 py_only : bool (default False)
2978 2978 Only try to fetch python code, do not try alternative methods to decode file
2979 2979 if unicode fails.
2980 2980
2981 2981 Returns
2982 2982 -------
2983 2983 A string of code.
2984 2984
2985 2985 ValueError is raised if nothing is found, and TypeError if it evaluates
2986 2986 to an object of another type. In each case, .args[0] is a printable
2987 2987 message.
2988 2988 """
2989 2989 code = self.extract_input_lines(target, raw=raw) # Grab history
2990 2990 if code:
2991 2991 return code
2992 2992 utarget = unquote_filename(target)
2993 2993 try:
2994 2994 if utarget.startswith(('http://', 'https://')):
2995 2995 return openpy.read_py_url(utarget, skip_encoding_cookie=skip_encoding_cookie)
2996 2996 except UnicodeDecodeError:
2997 2997 if not py_only :
2998 2998 from urllib import urlopen # Deferred import
2999 2999 response = urlopen(target)
3000 3000 return response.read().decode('latin1')
3001 3001 raise ValueError(("'%s' seem to be unreadable.") % utarget)
3002 3002
3003 3003 potential_target = [target]
3004 3004 try :
3005 3005 potential_target.insert(0,get_py_filename(target))
3006 3006 except IOError:
3007 3007 pass
3008 3008
3009 3009 for tgt in potential_target :
3010 3010 if os.path.isfile(tgt): # Read file
3011 3011 try :
3012 3012 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3013 3013 except UnicodeDecodeError :
3014 3014 if not py_only :
3015 3015 with io_open(tgt,'r', encoding='latin1') as f :
3016 3016 return f.read()
3017 3017 raise ValueError(("'%s' seem to be unreadable.") % target)
3018 3018 elif os.path.isdir(os.path.expanduser(tgt)):
3019 3019 raise ValueError("'%s' is a directory, not a regular file." % target)
3020 3020
3021 3021 try: # User namespace
3022 3022 codeobj = eval(target, self.user_ns)
3023 3023 except Exception:
3024 3024 raise ValueError(("'%s' was not found in history, as a file, url, "
3025 3025 "nor in the user namespace.") % target)
3026 3026 if isinstance(codeobj, basestring):
3027 3027 return codeobj
3028 3028 elif isinstance(codeobj, Macro):
3029 3029 return codeobj.value
3030 3030
3031 3031 raise TypeError("%s is neither a string nor a macro." % target,
3032 3032 codeobj)
3033 3033
3034 3034 #-------------------------------------------------------------------------
3035 3035 # Things related to IPython exiting
3036 3036 #-------------------------------------------------------------------------
3037 3037 def atexit_operations(self):
3038 3038 """This will be executed at the time of exit.
3039 3039
3040 3040 Cleanup operations and saving of persistent data that is done
3041 3041 unconditionally by IPython should be performed here.
3042 3042
3043 3043 For things that may depend on startup flags or platform specifics (such
3044 3044 as having readline or not), register a separate atexit function in the
3045 3045 code that has the appropriate information, rather than trying to
3046 3046 clutter
3047 3047 """
3048 3048 # Close the history session (this stores the end time and line count)
3049 3049 # this must be *before* the tempfile cleanup, in case of temporary
3050 3050 # history db
3051 3051 self.history_manager.end_session()
3052 3052
3053 3053 # Cleanup all tempfiles left around
3054 3054 for tfile in self.tempfiles:
3055 3055 try:
3056 3056 os.unlink(tfile)
3057 3057 except OSError:
3058 3058 pass
3059 3059
3060 3060 # Clear all user namespaces to release all references cleanly.
3061 3061 self.reset(new_session=False)
3062 3062
3063 3063 # Run user hooks
3064 3064 self.hooks.shutdown_hook()
3065 3065
3066 3066 def cleanup(self):
3067 3067 self.restore_sys_module_state()
3068 3068
3069 3069
3070 3070 class InteractiveShellABC(object):
3071 3071 """An abstract base class for InteractiveShell."""
3072 3072 __metaclass__ = abc.ABCMeta
3073 3073
3074 3074 InteractiveShellABC.register(InteractiveShell)
@@ -1,362 +1,391
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import shutil
28 28 import signal
29 29 import sys
30 30 import uuid
31 31
32 32
33 33 # Local imports
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
38 from IPython.kernel.kernelmanager import KernelManager
37 from IPython.kernel.blocking import BlockingKernelClient
38 from IPython.kernel import KernelManager
39 39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 40 from IPython.utils.path import filefind
41 41 from IPython.utils.py3compat import str_to_bytes
42 42 from IPython.utils.traitlets import (
43 43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 44 )
45 45 from IPython.kernel.zmq.kernelapp import (
46 46 kernel_flags,
47 47 kernel_aliases,
48 48 IPKernelApp
49 49 )
50 50 from IPython.kernel.zmq.session import Session, default_secure
51 51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Network Constants
55 55 #-----------------------------------------------------------------------------
56 56
57 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Aliases and Flags
66 66 #-----------------------------------------------------------------------------
67 67
68 68 flags = dict(kernel_flags)
69 69
70 70 # the flags that are specific to the frontend
71 71 # these must be scrubbed before being passed to the kernel,
72 72 # or it will raise an error on unrecognized flags
73 73 app_flags = {
74 74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 76 }
77 77 app_flags.update(boolean_flag(
78 78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 80 to force a direct exit without any confirmation.
81 81 """,
82 82 """Don't prompt the user when exiting. This will terminate the kernel
83 83 if it is owned by the frontend, and leave it alive if it is external.
84 84 """
85 85 ))
86 86 flags.update(app_flags)
87 87
88 88 aliases = dict(kernel_aliases)
89 89
90 90 # also scrub aliases from the frontend
91 91 app_aliases = dict(
92 92 ip = 'KernelManager.ip',
93 93 transport = 'KernelManager.transport',
94 94 hb = 'IPythonConsoleApp.hb_port',
95 95 shell = 'IPythonConsoleApp.shell_port',
96 96 iopub = 'IPythonConsoleApp.iopub_port',
97 97 stdin = 'IPythonConsoleApp.stdin_port',
98 98 existing = 'IPythonConsoleApp.existing',
99 99 f = 'IPythonConsoleApp.connection_file',
100 100
101 101
102 102 ssh = 'IPythonConsoleApp.sshserver',
103 103 )
104 104 aliases.update(app_aliases)
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Classes
108 108 #-----------------------------------------------------------------------------
109 109
110 110 #-----------------------------------------------------------------------------
111 111 # IPythonConsole
112 112 #-----------------------------------------------------------------------------
113 113
114 114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115 115
116 116 try:
117 117 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
118 118 except ImportError:
119 119 pass
120 120 else:
121 121 classes.append(InlineBackend)
122 122
123 123 class IPythonConsoleApp(Configurable):
124 124 name = 'ipython-console-mixin'
125 125 default_config_file_name='ipython_config.py'
126 126
127 127 description = """
128 128 The IPython Mixin Console.
129 129
130 130 This class contains the common portions of console client (QtConsole,
131 131 ZMQ-based terminal console, etc). It is not a full console, in that
132 132 launched terminal subprocesses will not be able to accept input.
133 133
134 134 The Console using this mixing supports various extra features beyond
135 135 the single-process Terminal IPython shell, such as connecting to
136 136 existing kernel, via:
137 137
138 138 ipython <appname> --existing
139 139
140 140 as well as tunnel via SSH
141 141
142 142 """
143 143
144 144 classes = classes
145 145 flags = Dict(flags)
146 146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = KernelManager
148 kernel_client_class = BlockingKernelClient
148 149
149 150 kernel_argv = List(Unicode)
150 151 # frontend flags&aliases to be stripped when building kernel_argv
151 152 frontend_flags = Any(app_flags)
152 153 frontend_aliases = Any(app_aliases)
153 154
154 155 # create requested profiles by default, if they don't exist:
155 156 auto_create = CBool(True)
156 157 # connection info:
157 158
158 159 sshserver = Unicode('', config=True,
159 160 help="""The SSH server to use to connect to the kernel.""")
160 161 sshkey = Unicode('', config=True,
161 162 help="""Path to the ssh key to use for logging in to the ssh server.""")
162 163
163 164 hb_port = Int(0, config=True,
164 165 help="set the heartbeat port [default: random]")
165 166 shell_port = Int(0, config=True,
166 167 help="set the shell (ROUTER) port [default: random]")
167 168 iopub_port = Int(0, config=True,
168 169 help="set the iopub (PUB) port [default: random]")
169 170 stdin_port = Int(0, config=True,
170 171 help="set the stdin (DEALER) port [default: random]")
171 172 connection_file = Unicode('', config=True,
172 173 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173 174
174 175 This file will contain the IP, ports, and authentication key needed to connect
175 176 clients to this kernel. By default, this file will be created in the security-dir
176 177 of the current profile, but can be specified by absolute path.
177 178 """)
178 179 def _connection_file_default(self):
179 180 return 'kernel-%i.json' % os.getpid()
180 181
181 182 existing = CUnicode('', config=True,
182 183 help="""Connect to an already running kernel""")
183 184
184 185 confirm_exit = CBool(True, config=True,
185 186 help="""
186 187 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 188 to force a direct exit without any confirmation.""",
188 189 )
189 190
190 191
191 192 def build_kernel_argv(self, argv=None):
192 193 """build argv to be passed to kernel subprocess"""
193 194 if argv is None:
194 195 argv = sys.argv[1:]
195 196 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 197 # kernel should inherit default config file from frontend
197 198 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
198 199
199 200 def init_connection_file(self):
200 201 """find the connection file, and load the info if found.
201 202
202 203 The current working directory and the current profile's security
203 204 directory will be searched for the file if it is not given by
204 205 absolute path.
205 206
206 207 When attempting to connect to an existing kernel and the `--existing`
207 208 argument does not match an existing file, it will be interpreted as a
208 209 fileglob, and the matching file in the current profile's security dir
209 210 with the latest access time will be used.
210 211
211 212 After this method is called, self.connection_file contains the *full path*
212 213 to the connection file, never just its name.
213 214 """
214 215 if self.existing:
215 216 try:
216 217 cf = find_connection_file(self.existing)
217 218 except Exception:
218 219 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 220 self.exit(1)
220 221 self.log.info("Connecting to existing kernel: %s" % cf)
221 222 self.connection_file = cf
222 223 else:
223 224 # not existing, check if we are going to write the file
224 225 # and ensure that self.connection_file is a full path, not just the shortname
225 226 try:
226 227 cf = find_connection_file(self.connection_file)
227 228 except Exception:
228 229 # file might not exist
229 230 if self.connection_file == os.path.basename(self.connection_file):
230 231 # just shortname, put it in security dir
231 232 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 233 else:
233 234 cf = self.connection_file
234 235 self.connection_file = cf
235 236
236 237 # should load_connection_file only be used for existing?
237 238 # as it is now, this allows reusing ports if an existing
238 239 # file is requested
239 240 try:
240 241 self.load_connection_file()
241 242 except Exception:
242 243 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 244 self.exit(1)
244 245
245 246 def load_connection_file(self):
246 247 """load ip/port/hmac config from JSON connection file"""
247 248 # this is identical to IPKernelApp.load_connection_file
248 249 # perhaps it can be centralized somewhere?
249 250 try:
250 251 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 252 except IOError:
252 253 self.log.debug("Connection File not found: %s", self.connection_file)
253 254 return
254 255 self.log.debug(u"Loading connection file %s", fname)
255 256 with open(fname) as f:
256 257 cfg = json.load(f)
257 258
258 259 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 260 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260 261
261 262 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 263 name = channel + '_port'
263 264 if getattr(self, name) == 0 and name in cfg:
264 265 # not overridden by config or cl_args
265 266 setattr(self, name, cfg[name])
266 267 if 'key' in cfg:
267 268 self.config.Session.key = str_to_bytes(cfg['key'])
268 269
269 270 def init_ssh(self):
270 271 """set up ssh tunnels, if needed."""
271 272 if not self.existing or (not self.sshserver and not self.sshkey):
272 273 return
273 274
274 275 self.load_connection_file()
275 276
276 277 transport = self.config.KernelManager.transport
277 278 ip = self.config.KernelManager.ip
278 279
279 280 if transport != 'tcp':
280 281 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 282 sys.exit(-1)
282 283
283 284 if self.sshkey and not self.sshserver:
284 285 # specifying just the key implies that we are connecting directly
285 286 self.sshserver = ip
286 287 ip = LOCALHOST
287 288
288 289 # build connection dict for tunnels:
289 290 info = dict(ip=ip,
290 291 shell_port=self.shell_port,
291 292 iopub_port=self.iopub_port,
292 293 stdin_port=self.stdin_port,
293 294 hb_port=self.hb_port
294 295 )
295 296
296 297 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297 298
298 299 # tunnels return a new set of ports, which will be on localhost:
299 300 self.config.KernelManager.ip = LOCALHOST
300 301 try:
301 302 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 303 except:
303 304 # even catch KeyboardInterrupt
304 305 self.log.error("Could not setup tunnels", exc_info=True)
305 306 self.exit(1)
306 307
307 308 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308 309
309 310 cf = self.connection_file
310 311 base,ext = os.path.splitext(cf)
311 312 base = os.path.basename(base)
312 313 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 314 self.log.critical("To connect another client via this tunnel, use:")
314 315 self.log.critical("--existing %s" % self.connection_file)
315 316
316 317 def _new_connection_file(self):
317 318 cf = ''
318 319 while not cf:
319 320 # we don't need a 128b id to distinguish kernels, use more readable
320 321 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 322 # kernels can subclass.
322 323 ident = str(uuid.uuid4()).split('-')[-1]
323 324 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 325 # only keep if it's actually new. Protect against unlikely collision
325 326 # in 48b random search space
326 327 cf = cf if not os.path.exists(cf) else ''
327 328 return cf
328 329
329 330 def init_kernel_manager(self):
330 331 # Don't let Qt or ZMQ swallow KeyboardInterupts.
332 if self.existing:
333 self.kernel_manager = None
334 return
331 335 signal.signal(signal.SIGINT, signal.SIG_DFL)
332 336
333 337 # Create a KernelManager and start a kernel.
334 338 self.kernel_manager = self.kernel_manager_class(
335 339 shell_port=self.shell_port,
336 340 iopub_port=self.iopub_port,
337 341 stdin_port=self.stdin_port,
338 342 hb_port=self.hb_port,
339 343 connection_file=self.connection_file,
340 344 config=self.config,
341 345 )
342 # start the kernel
343 if not self.existing:
346 self.kernel_manager.client_factory = self.kernel_client_class
344 347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 348 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 elif self.sshserver:
349
350 if self.sshserver:
347 351 # ssh, write new connection file
348 352 self.kernel_manager.write_connection_file()
353
354 # in case KM defaults / ssh writing changes things:
355 km = self.kernel_manager
356 self.shell_port=km.shell_port
357 self.iopub_port=km.iopub_port
358 self.stdin_port=km.stdin_port
359 self.hb_port=km.hb_port
360 self.connection_file = km.connection_file
361
349 362 atexit.register(self.kernel_manager.cleanup_connection_file)
350 self.kernel_manager.start_channels()
363
364 def init_kernel_client(self):
365 if self.kernel_manager is not None:
366 self.kernel_client = self.kernel_manager.client()
367 else:
368 self.kernel_client = self.kernel_client_class(
369 shell_port=self.shell_port,
370 iopub_port=self.iopub_port,
371 stdin_port=self.stdin_port,
372 hb_port=self.hb_port,
373 connection_file=self.connection_file,
374 config=self.config,
375 )
376
377 self.kernel_client.start_channels()
378
351 379
352 380
353 381 def initialize(self, argv=None):
354 382 """
355 383 Classes which mix this class in should call:
356 384 IPythonConsoleApp.initialize(self,argv)
357 385 """
358 386 self.init_connection_file()
359 387 default_secure(self.config)
360 388 self.init_ssh()
361 389 self.init_kernel_manager()
390 self.init_kernel_client()
362 391
@@ -1,919 +1,876
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import Cookie
20 20 import datetime
21 21 import email.utils
22 22 import hashlib
23 23 import logging
24 24 import mimetypes
25 25 import os
26 26 import stat
27 27 import threading
28 28 import time
29 29 import uuid
30 30
31 31 from tornado.escape import url_escape
32 32 from tornado import web
33 33 from tornado import websocket
34 34
35 35 from zmq.eventloop import ioloop
36 36 from zmq.utils import jsonapi
37 37
38 38 from IPython.external.decorator import decorator
39 39 from IPython.kernel.zmq.session import Session
40 40 from IPython.lib.security import passwd_check
41 41 from IPython.utils.jsonutil import date_default
42 42 from IPython.utils.path import filefind
43 43 from IPython.utils.py3compat import PY3
44 44
45 45 try:
46 46 from docutils.core import publish_string
47 47 except ImportError:
48 48 publish_string = None
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
52 52 #-----------------------------------------------------------------------------
53 53
54 54 # Google Chrome, as of release 16, changed its websocket protocol number. The
55 55 # parts tornado cares about haven't really changed, so it's OK to continue
56 56 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
57 57 # version as of Oct 30/2011) the version check fails, see the issue report:
58 58
59 59 # https://github.com/facebook/tornado/issues/385
60 60
61 61 # This issue has been fixed in Tornado post 2.1.1:
62 62
63 63 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
64 64
65 65 # Here we manually apply the same patch as above so that users of IPython can
66 66 # continue to work with an officially released Tornado. We make the
67 67 # monkeypatch version check as narrow as possible to limit its effects; once
68 68 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
69 69
70 70 import tornado
71 71
72 72 if tornado.version_info <= (2,1,1):
73 73
74 74 def _execute(self, transforms, *args, **kwargs):
75 75 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
76 76
77 77 self.open_args = args
78 78 self.open_kwargs = kwargs
79 79
80 80 # The difference between version 8 and 13 is that in 8 the
81 81 # client sends a "Sec-Websocket-Origin" header and in 13 it's
82 82 # simply "Origin".
83 83 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
84 84 self.ws_connection = WebSocketProtocol8(self)
85 85 self.ws_connection.accept_connection()
86 86
87 87 elif self.request.headers.get("Sec-WebSocket-Version"):
88 88 self.stream.write(tornado.escape.utf8(
89 89 "HTTP/1.1 426 Upgrade Required\r\n"
90 90 "Sec-WebSocket-Version: 8\r\n\r\n"))
91 91 self.stream.close()
92 92
93 93 else:
94 94 self.ws_connection = WebSocketProtocol76(self)
95 95 self.ws_connection.accept_connection()
96 96
97 97 websocket.WebSocketHandler._execute = _execute
98 98 del _execute
99 99
100 100 #-----------------------------------------------------------------------------
101 101 # Decorator for disabling read-only handlers
102 102 #-----------------------------------------------------------------------------
103 103
104 104 @decorator
105 105 def not_if_readonly(f, self, *args, **kwargs):
106 106 if self.application.read_only:
107 107 raise web.HTTPError(403, "Notebook server is read-only")
108 108 else:
109 109 return f(self, *args, **kwargs)
110 110
111 111 @decorator
112 112 def authenticate_unless_readonly(f, self, *args, **kwargs):
113 113 """authenticate this page *unless* readonly view is active.
114 114
115 115 In read-only mode, the notebook list and print view should
116 116 be accessible without authentication.
117 117 """
118 118
119 119 @web.authenticated
120 120 def auth_f(self, *args, **kwargs):
121 121 return f(self, *args, **kwargs)
122 122
123 123 if self.application.read_only:
124 124 return f(self, *args, **kwargs)
125 125 else:
126 126 return auth_f(self, *args, **kwargs)
127 127
128 128 def urljoin(*pieces):
129 129 """Join componenet of url into a relative url
130 130
131 131 Use to prevent double slash when joining subpath
132 132 """
133 133 striped = [s.strip('/') for s in pieces]
134 134 return '/'.join(s for s in striped if s)
135 135
136 136 #-----------------------------------------------------------------------------
137 137 # Top-level handlers
138 138 #-----------------------------------------------------------------------------
139 139
140 140 class RequestHandler(web.RequestHandler):
141 141 """RequestHandler with default variable setting."""
142 142
143 143 def render(*args, **kwargs):
144 144 kwargs.setdefault('message', '')
145 145 return web.RequestHandler.render(*args, **kwargs)
146 146
147 147 class AuthenticatedHandler(RequestHandler):
148 148 """A RequestHandler with an authenticated user."""
149 149
150 150 def get_current_user(self):
151 151 user_id = self.get_secure_cookie(self.settings['cookie_name'])
152 152 # For now the user_id should not return empty, but it could eventually
153 153 if user_id == '':
154 154 user_id = 'anonymous'
155 155 if user_id is None:
156 156 # prevent extra Invalid cookie sig warnings:
157 157 self.clear_cookie(self.settings['cookie_name'])
158 158 if not self.application.password and not self.application.read_only:
159 159 user_id = 'anonymous'
160 160 return user_id
161 161
162 162 @property
163 163 def logged_in(self):
164 164 """Is a user currently logged in?
165 165
166 166 """
167 167 user = self.get_current_user()
168 168 return (user and not user == 'anonymous')
169 169
170 170 @property
171 171 def login_available(self):
172 172 """May a user proceed to log in?
173 173
174 174 This returns True if login capability is available, irrespective of
175 175 whether the user is already logged in or not.
176 176
177 177 """
178 178 return bool(self.application.password)
179 179
180 180 @property
181 181 def read_only(self):
182 182 """Is the notebook read-only?
183 183
184 184 """
185 185 return self.application.read_only
186 186
187 187 @property
188 188 def use_less(self):
189 189 """Use less instead of css in templates"""
190 190 return self.application.use_less
191 191
192 192 @property
193 193 def ws_url(self):
194 194 """websocket url matching the current request
195 195
196 196 turns http[s]://host[:port] into
197 197 ws[s]://host[:port]
198 198 """
199 199 proto = self.request.protocol.replace('http', 'ws')
200 200 host = self.application.ipython_app.websocket_host # default to config value
201 201 if host == '':
202 202 host = self.request.host # get from request
203 203 return "%s://%s" % (proto, host)
204 204
205 205
206 206 class AuthenticatedFileHandler(AuthenticatedHandler, web.StaticFileHandler):
207 207 """static files should only be accessible when logged in"""
208 208
209 209 @authenticate_unless_readonly
210 210 def get(self, path):
211 211 return web.StaticFileHandler.get(self, path)
212 212
213 213
214 214 class ProjectDashboardHandler(AuthenticatedHandler):
215 215
216 216 @authenticate_unless_readonly
217 217 def get(self):
218 218 nbm = self.application.notebook_manager
219 219 project = nbm.notebook_dir
220 220 template = self.application.jinja2_env.get_template('projectdashboard.html')
221 221 self.write( template.render(
222 222 project=project,
223 223 project_component=project.split('/'),
224 224 base_project_url=self.application.ipython_app.base_project_url,
225 225 base_kernel_url=self.application.ipython_app.base_kernel_url,
226 226 read_only=self.read_only,
227 227 logged_in=self.logged_in,
228 228 use_less=self.use_less,
229 229 login_available=self.login_available))
230 230
231 231
232 232 class LoginHandler(AuthenticatedHandler):
233 233
234 234 def _render(self, message=None):
235 235 template = self.application.jinja2_env.get_template('login.html')
236 236 self.write( template.render(
237 237 next=url_escape(self.get_argument('next', default=self.application.ipython_app.base_project_url)),
238 238 read_only=self.read_only,
239 239 logged_in=self.logged_in,
240 240 login_available=self.login_available,
241 241 base_project_url=self.application.ipython_app.base_project_url,
242 242 message=message
243 243 ))
244 244
245 245 def get(self):
246 246 if self.current_user:
247 247 self.redirect(self.get_argument('next', default=self.application.ipython_app.base_project_url))
248 248 else:
249 249 self._render()
250 250
251 251 def post(self):
252 252 pwd = self.get_argument('password', default=u'')
253 253 if self.application.password:
254 254 if passwd_check(self.application.password, pwd):
255 255 self.set_secure_cookie(self.settings['cookie_name'], str(uuid.uuid4()))
256 256 else:
257 257 self._render(message={'error': 'Invalid password'})
258 258 return
259 259
260 260 self.redirect(self.get_argument('next', default=self.application.ipython_app.base_project_url))
261 261
262 262
263 263 class LogoutHandler(AuthenticatedHandler):
264 264
265 265 def get(self):
266 266 self.clear_cookie(self.settings['cookie_name'])
267 267 if self.login_available:
268 268 message = {'info': 'Successfully logged out.'}
269 269 else:
270 270 message = {'warning': 'Cannot log out. Notebook authentication '
271 271 'is disabled.'}
272 272 template = self.application.jinja2_env.get_template('logout.html')
273 273 self.write( template.render(
274 274 read_only=self.read_only,
275 275 logged_in=self.logged_in,
276 276 login_available=self.login_available,
277 277 base_project_url=self.application.ipython_app.base_project_url,
278 278 message=message))
279 279
280 280
281 281 class NewHandler(AuthenticatedHandler):
282 282
283 283 @web.authenticated
284 284 def get(self):
285 285 nbm = self.application.notebook_manager
286 286 project = nbm.notebook_dir
287 287 notebook_id = nbm.new_notebook()
288 288 self.redirect('/'+urljoin(self.application.ipython_app.base_project_url, notebook_id))
289 289
290 290 class NamedNotebookHandler(AuthenticatedHandler):
291 291
292 292 @authenticate_unless_readonly
293 293 def get(self, notebook_id):
294 294 nbm = self.application.notebook_manager
295 295 project = nbm.notebook_dir
296 296 if not nbm.notebook_exists(notebook_id):
297 297 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
298 298 template = self.application.jinja2_env.get_template('notebook.html')
299 299 self.write( template.render(
300 300 project=project,
301 301 notebook_id=notebook_id,
302 302 base_project_url=self.application.ipython_app.base_project_url,
303 303 base_kernel_url=self.application.ipython_app.base_kernel_url,
304 304 kill_kernel=False,
305 305 read_only=self.read_only,
306 306 logged_in=self.logged_in,
307 307 login_available=self.login_available,
308 308 mathjax_url=self.application.ipython_app.mathjax_url,
309 309 use_less=self.use_less
310 310 )
311 311 )
312 312
313 313
314 314 class PrintNotebookHandler(AuthenticatedHandler):
315 315
316 316 @authenticate_unless_readonly
317 317 def get(self, notebook_id):
318 318 nbm = self.application.notebook_manager
319 319 project = nbm.notebook_dir
320 320 if not nbm.notebook_exists(notebook_id):
321 321 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
322 322 template = self.application.jinja2_env.get_template('printnotebook.html')
323 323 self.write( template.render(
324 324 project=project,
325 325 notebook_id=notebook_id,
326 326 base_project_url=self.application.ipython_app.base_project_url,
327 327 base_kernel_url=self.application.ipython_app.base_kernel_url,
328 328 kill_kernel=False,
329 329 read_only=self.read_only,
330 330 logged_in=self.logged_in,
331 331 login_available=self.login_available,
332 332 mathjax_url=self.application.ipython_app.mathjax_url,
333 333 ))
334 334
335 335 #-----------------------------------------------------------------------------
336 336 # Kernel handlers
337 337 #-----------------------------------------------------------------------------
338 338
339 339
340 340 class MainKernelHandler(AuthenticatedHandler):
341 341
342 342 @web.authenticated
343 343 def get(self):
344 344 km = self.application.kernel_manager
345 345 self.finish(jsonapi.dumps(km.list_kernel_ids()))
346 346
347 347 @web.authenticated
348 348 def post(self):
349 349 km = self.application.kernel_manager
350 350 nbm = self.application.notebook_manager
351 351 notebook_id = self.get_argument('notebook', default=None)
352 352 kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
353 353 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
354 354 self.set_header('Location', '/'+kernel_id)
355 355 self.finish(jsonapi.dumps(data))
356 356
357 357
358 358 class KernelHandler(AuthenticatedHandler):
359 359
360 360 SUPPORTED_METHODS = ('DELETE')
361 361
362 362 @web.authenticated
363 363 def delete(self, kernel_id):
364 364 km = self.application.kernel_manager
365 365 km.shutdown_kernel(kernel_id)
366 366 self.set_status(204)
367 367 self.finish()
368 368
369 369
370 370 class KernelActionHandler(AuthenticatedHandler):
371 371
372 372 @web.authenticated
373 373 def post(self, kernel_id, action):
374 374 km = self.application.kernel_manager
375 375 if action == 'interrupt':
376 376 km.interrupt_kernel(kernel_id)
377 377 self.set_status(204)
378 378 if action == 'restart':
379 379 km.restart_kernel(kernel_id)
380 380 data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
381 381 self.set_header('Location', '/'+kernel_id)
382 382 self.write(jsonapi.dumps(data))
383 383 self.finish()
384 384
385 385
386 386 class ZMQStreamHandler(websocket.WebSocketHandler):
387 387
388 388 def _reserialize_reply(self, msg_list):
389 389 """Reserialize a reply message using JSON.
390 390
391 391 This takes the msg list from the ZMQ socket, unserializes it using
392 392 self.session and then serializes the result using JSON. This method
393 393 should be used by self._on_zmq_reply to build messages that can
394 394 be sent back to the browser.
395 395 """
396 396 idents, msg_list = self.session.feed_identities(msg_list)
397 397 msg = self.session.unserialize(msg_list)
398 398 try:
399 399 msg['header'].pop('date')
400 400 except KeyError:
401 401 pass
402 402 try:
403 403 msg['parent_header'].pop('date')
404 404 except KeyError:
405 405 pass
406 406 msg.pop('buffers')
407 407 return jsonapi.dumps(msg, default=date_default)
408 408
409 409 def _on_zmq_reply(self, msg_list):
410 # Sometimes this gets triggered when the on_close method is scheduled in the
411 # eventloop but hasn't been called.
412 if self.stream.closed(): return
410 413 try:
411 414 msg = self._reserialize_reply(msg_list)
412 415 except Exception:
413 416 self.application.log.critical("Malformed message: %r" % msg_list, exc_info=True)
414 417 else:
415 418 self.write_message(msg)
416 419
417 420 def allow_draft76(self):
418 421 """Allow draft 76, until browsers such as Safari update to RFC 6455.
419 422
420 423 This has been disabled by default in tornado in release 2.2.0, and
421 424 support will be removed in later versions.
422 425 """
423 426 return True
424 427
425 428
426 429 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
427 430
428 431 def open(self, kernel_id):
429 432 self.kernel_id = kernel_id.decode('ascii')
430 433 try:
431 434 cfg = self.application.config
432 435 except AttributeError:
433 436 # protect from the case where this is run from something other than
434 437 # the notebook app:
435 438 cfg = None
436 439 self.session = Session(config=cfg)
437 440 self.save_on_message = self.on_message
438 441 self.on_message = self.on_first_message
439 442
440 443 def get_current_user(self):
441 444 user_id = self.get_secure_cookie(self.settings['cookie_name'])
442 445 if user_id == '' or (user_id is None and not self.application.password):
443 446 user_id = 'anonymous'
444 447 return user_id
445 448
446 449 def _inject_cookie_message(self, msg):
447 450 """Inject the first message, which is the document cookie,
448 451 for authentication."""
449 452 if not PY3 and isinstance(msg, unicode):
450 453 # Cookie constructor doesn't accept unicode strings
451 454 # under Python 2.x for some reason
452 455 msg = msg.encode('utf8', 'replace')
453 456 try:
454 457 self.request._cookies = Cookie.SimpleCookie(msg)
455 458 except:
456 459 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
457 460
458 461 def on_first_message(self, msg):
459 462 self._inject_cookie_message(msg)
460 463 if self.get_current_user() is None:
461 464 logging.warn("Couldn't authenticate WebSocket connection")
462 465 raise web.HTTPError(403)
463 466 self.on_message = self.save_on_message
464 467
465 468
466 469 class IOPubHandler(AuthenticatedZMQStreamHandler):
467 470
468 471 def initialize(self, *args, **kwargs):
469 self._kernel_alive = True
470 self._beating = False
471 472 self.iopub_stream = None
472 self.hb_stream = None
473 473
474 474 def on_first_message(self, msg):
475 475 try:
476 476 super(IOPubHandler, self).on_first_message(msg)
477 477 except web.HTTPError:
478 478 self.close()
479 479 return
480 480 km = self.application.kernel_manager
481 self.time_to_dead = km.time_to_dead
482 self.first_beat = km.first_beat
483 481 kernel_id = self.kernel_id
482 km.add_restart_callback(kernel_id, self.on_kernel_restarted)
483 km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
484 484 try:
485 self.iopub_stream = km.create_iopub_stream(kernel_id)
486 self.hb_stream = km.create_hb_stream(kernel_id)
485 self.iopub_stream = km.connect_iopub(kernel_id)
487 486 except web.HTTPError:
488 487 # WebSockets don't response to traditional error codes so we
489 488 # close the connection.
490 489 if not self.stream.closed():
491 490 self.stream.close()
492 491 self.close()
493 492 else:
494 493 self.iopub_stream.on_recv(self._on_zmq_reply)
495 self.start_hb(self.kernel_died)
496 494
497 495 def on_message(self, msg):
498 496 pass
499 497
498 def _send_status_message(self, status):
499 msg = self.session.msg("status",
500 {'execution_state': status}
501 )
502 self.write_message(jsonapi.dumps(msg, default=date_default))
503
504 def on_kernel_restarted(self):
505 logging.warn("kernel %s restarted", self.kernel_id)
506 self._send_status_message('restarting')
507
508 def on_restart_failed(self):
509 logging.error("kernel %s restarted failed!", self.kernel_id)
510 self._send_status_message('dead')
511
500 512 def on_close(self):
501 513 # This method can be called twice, once by self.kernel_died and once
502 514 # from the WebSocket close event. If the WebSocket connection is
503 515 # closed before the ZMQ streams are setup, they could be None.
504 self.stop_hb()
516 km = self.application.kernel_manager
517 if self.kernel_id in km:
518 km.remove_restart_callback(
519 self.kernel_id, self.on_kernel_restarted,
520 )
521 km.remove_restart_callback(
522 self.kernel_id, self.on_restart_failed, 'dead',
523 )
505 524 if self.iopub_stream is not None and not self.iopub_stream.closed():
506 525 self.iopub_stream.on_recv(None)
507 526 self.iopub_stream.close()
508 if self.hb_stream is not None and not self.hb_stream.closed():
509 self.hb_stream.close()
510
511 def start_hb(self, callback):
512 """Start the heartbeating and call the callback if the kernel dies."""
513 if not self._beating:
514 self._kernel_alive = True
515
516 def ping_or_dead():
517 self.hb_stream.flush()
518 if self._kernel_alive:
519 self._kernel_alive = False
520 self.hb_stream.send(b'ping')
521 # flush stream to force immediate socket send
522 self.hb_stream.flush()
523 else:
524 try:
525 callback()
526 except:
527 pass
528 finally:
529 self.stop_hb()
530
531 def beat_received(msg):
532 self._kernel_alive = True
533
534 self.hb_stream.on_recv(beat_received)
535 loop = ioloop.IOLoop.instance()
536 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000, loop)
537 loop.add_timeout(time.time()+self.first_beat, self._really_start_hb)
538 self._beating= True
539
540 def _really_start_hb(self):
541 """callback for delayed heartbeat start
542
543 Only start the hb loop if we haven't been closed during the wait.
544 """
545 if self._beating and not self.hb_stream.closed():
546 self._hb_periodic_callback.start()
547
548 def stop_hb(self):
549 """Stop the heartbeating and cancel all related callbacks."""
550 if self._beating:
551 self._beating = False
552 self._hb_periodic_callback.stop()
553 if not self.hb_stream.closed():
554 self.hb_stream.on_recv(None)
555
556 def _delete_kernel_data(self):
557 """Remove the kernel data and notebook mapping."""
558 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
559
560 def kernel_died(self):
561 self._delete_kernel_data()
562 self.application.log.error("Kernel died: %s" % self.kernel_id)
563 self.write_message(
564 {'header': {'msg_type': 'status'},
565 'parent_header': {},
566 'content': {'execution_state':'dead'}
567 }
568 )
569 self.on_close()
570 527
571 528
572 529 class ShellHandler(AuthenticatedZMQStreamHandler):
573 530
574 531 def initialize(self, *args, **kwargs):
575 532 self.shell_stream = None
576 533
577 534 def on_first_message(self, msg):
578 535 try:
579 536 super(ShellHandler, self).on_first_message(msg)
580 537 except web.HTTPError:
581 538 self.close()
582 539 return
583 540 km = self.application.kernel_manager
584 541 self.max_msg_size = km.max_msg_size
585 542 kernel_id = self.kernel_id
586 543 try:
587 self.shell_stream = km.create_shell_stream(kernel_id)
544 self.shell_stream = km.connect_shell(kernel_id)
588 545 except web.HTTPError:
589 546 # WebSockets don't response to traditional error codes so we
590 547 # close the connection.
591 548 if not self.stream.closed():
592 549 self.stream.close()
593 550 self.close()
594 551 else:
595 552 self.shell_stream.on_recv(self._on_zmq_reply)
596 553
597 554 def on_message(self, msg):
598 555 if len(msg) < self.max_msg_size:
599 556 msg = jsonapi.loads(msg)
600 557 self.session.send(self.shell_stream, msg)
601 558
602 559 def on_close(self):
603 560 # Make sure the stream exists and is not already closed.
604 561 if self.shell_stream is not None and not self.shell_stream.closed():
605 562 self.shell_stream.close()
606 563
607 564
608 565 #-----------------------------------------------------------------------------
609 566 # Notebook web service handlers
610 567 #-----------------------------------------------------------------------------
611 568
612 569 class NotebookRedirectHandler(AuthenticatedHandler):
613 570
614 571 @authenticate_unless_readonly
615 572 def get(self, notebook_name):
616 573 app = self.application
617 574 # strip trailing .ipynb:
618 575 notebook_name = os.path.splitext(notebook_name)[0]
619 576 notebook_id = app.notebook_manager.rev_mapping.get(notebook_name, '')
620 577 if notebook_id:
621 578 url = self.settings.get('base_project_url', '/') + notebook_id
622 579 return self.redirect(url)
623 580 else:
624 581 raise HTTPError(404)
625 582
626 583 class NotebookRootHandler(AuthenticatedHandler):
627 584
628 585 @authenticate_unless_readonly
629 586 def get(self):
630 587 nbm = self.application.notebook_manager
631 588 km = self.application.kernel_manager
632 589 files = nbm.list_notebooks()
633 590 for f in files :
634 591 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
635 592 self.finish(jsonapi.dumps(files))
636 593
637 594 @web.authenticated
638 595 def post(self):
639 596 nbm = self.application.notebook_manager
640 597 body = self.request.body.strip()
641 598 format = self.get_argument('format', default='json')
642 599 name = self.get_argument('name', default=None)
643 600 if body:
644 601 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
645 602 else:
646 603 notebook_id = nbm.new_notebook()
647 604 self.set_header('Location', '/'+notebook_id)
648 605 self.finish(jsonapi.dumps(notebook_id))
649 606
650 607
651 608 class NotebookHandler(AuthenticatedHandler):
652 609
653 610 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
654 611
655 612 @authenticate_unless_readonly
656 613 def get(self, notebook_id):
657 614 nbm = self.application.notebook_manager
658 615 format = self.get_argument('format', default='json')
659 616 last_mod, name, data = nbm.get_notebook(notebook_id, format)
660 617
661 618 if format == u'json':
662 619 self.set_header('Content-Type', 'application/json')
663 620 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
664 621 elif format == u'py':
665 622 self.set_header('Content-Type', 'application/x-python')
666 623 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
667 624 self.set_header('Last-Modified', last_mod)
668 625 self.finish(data)
669 626
670 627 @web.authenticated
671 628 def put(self, notebook_id):
672 629 nbm = self.application.notebook_manager
673 630 format = self.get_argument('format', default='json')
674 631 name = self.get_argument('name', default=None)
675 632 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
676 633 self.set_status(204)
677 634 self.finish()
678 635
679 636 @web.authenticated
680 637 def delete(self, notebook_id):
681 638 nbm = self.application.notebook_manager
682 639 nbm.delete_notebook(notebook_id)
683 640 self.set_status(204)
684 641 self.finish()
685 642
686 643
687 644 class NotebookCopyHandler(AuthenticatedHandler):
688 645
689 646 @web.authenticated
690 647 def get(self, notebook_id):
691 648 nbm = self.application.notebook_manager
692 649 project = nbm.notebook_dir
693 650 notebook_id = nbm.copy_notebook(notebook_id)
694 651 self.redirect('/'+urljoin(self.application.ipython_app.base_project_url, notebook_id))
695 652
696 653
697 654 #-----------------------------------------------------------------------------
698 655 # Cluster handlers
699 656 #-----------------------------------------------------------------------------
700 657
701 658
702 659 class MainClusterHandler(AuthenticatedHandler):
703 660
704 661 @web.authenticated
705 662 def get(self):
706 663 cm = self.application.cluster_manager
707 664 self.finish(jsonapi.dumps(cm.list_profiles()))
708 665
709 666
710 667 class ClusterProfileHandler(AuthenticatedHandler):
711 668
712 669 @web.authenticated
713 670 def get(self, profile):
714 671 cm = self.application.cluster_manager
715 672 self.finish(jsonapi.dumps(cm.profile_info(profile)))
716 673
717 674
718 675 class ClusterActionHandler(AuthenticatedHandler):
719 676
720 677 @web.authenticated
721 678 def post(self, profile, action):
722 679 cm = self.application.cluster_manager
723 680 if action == 'start':
724 681 n = self.get_argument('n',default=None)
725 682 if n is None:
726 683 data = cm.start_cluster(profile)
727 684 else:
728 685 data = cm.start_cluster(profile,int(n))
729 686 if action == 'stop':
730 687 data = cm.stop_cluster(profile)
731 688 self.finish(jsonapi.dumps(data))
732 689
733 690
734 691 #-----------------------------------------------------------------------------
735 692 # RST web service handlers
736 693 #-----------------------------------------------------------------------------
737 694
738 695
739 696 class RSTHandler(AuthenticatedHandler):
740 697
741 698 @web.authenticated
742 699 def post(self):
743 700 if publish_string is None:
744 701 raise web.HTTPError(503, u'docutils not available')
745 702 body = self.request.body.strip()
746 703 source = body
747 704 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
748 705 defaults = {'file_insertion_enabled': 0,
749 706 'raw_enabled': 0,
750 707 '_disable_config': 1,
751 708 'stylesheet_path': 0
752 709 # 'template': template_path
753 710 }
754 711 try:
755 712 html = publish_string(source, writer_name='html',
756 713 settings_overrides=defaults
757 714 )
758 715 except:
759 716 raise web.HTTPError(400, u'Invalid RST')
760 717 print html
761 718 self.set_header('Content-Type', 'text/html')
762 719 self.finish(html)
763 720
764 721 # to minimize subclass changes:
765 722 HTTPError = web.HTTPError
766 723
767 724 class FileFindHandler(web.StaticFileHandler):
768 725 """subclass of StaticFileHandler for serving files from a search path"""
769 726
770 727 _static_paths = {}
771 728 # _lock is needed for tornado < 2.2.0 compat
772 729 _lock = threading.Lock() # protects _static_hashes
773 730
774 731 def initialize(self, path, default_filename=None):
775 732 if isinstance(path, basestring):
776 733 path = [path]
777 734 self.roots = tuple(
778 735 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
779 736 )
780 737 self.default_filename = default_filename
781 738
782 739 @classmethod
783 740 def locate_file(cls, path, roots):
784 741 """locate a file to serve on our static file search path"""
785 742 with cls._lock:
786 743 if path in cls._static_paths:
787 744 return cls._static_paths[path]
788 745 try:
789 746 abspath = os.path.abspath(filefind(path, roots))
790 747 except IOError:
791 748 # empty string should always give exists=False
792 749 return ''
793 750
794 751 # os.path.abspath strips a trailing /
795 752 # it needs to be temporarily added back for requests to root/
796 753 if not (abspath + os.path.sep).startswith(roots):
797 754 raise HTTPError(403, "%s is not in root static directory", path)
798 755
799 756 cls._static_paths[path] = abspath
800 757 return abspath
801 758
802 759 def get(self, path, include_body=True):
803 760 path = self.parse_url_path(path)
804 761
805 762 # begin subclass override
806 763 abspath = self.locate_file(path, self.roots)
807 764 # end subclass override
808 765
809 766 if os.path.isdir(abspath) and self.default_filename is not None:
810 767 # need to look at the request.path here for when path is empty
811 768 # but there is some prefix to the path that was already
812 769 # trimmed by the routing
813 770 if not self.request.path.endswith("/"):
814 771 self.redirect(self.request.path + "/")
815 772 return
816 773 abspath = os.path.join(abspath, self.default_filename)
817 774 if not os.path.exists(abspath):
818 775 raise HTTPError(404)
819 776 if not os.path.isfile(abspath):
820 777 raise HTTPError(403, "%s is not a file", path)
821 778
822 779 stat_result = os.stat(abspath)
823 780 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
824 781
825 782 self.set_header("Last-Modified", modified)
826 783
827 784 mime_type, encoding = mimetypes.guess_type(abspath)
828 785 if mime_type:
829 786 self.set_header("Content-Type", mime_type)
830 787
831 788 cache_time = self.get_cache_time(path, modified, mime_type)
832 789
833 790 if cache_time > 0:
834 791 self.set_header("Expires", datetime.datetime.utcnow() + \
835 792 datetime.timedelta(seconds=cache_time))
836 793 self.set_header("Cache-Control", "max-age=" + str(cache_time))
837 794 else:
838 795 self.set_header("Cache-Control", "public")
839 796
840 797 self.set_extra_headers(path)
841 798
842 799 # Check the If-Modified-Since, and don't send the result if the
843 800 # content has not been modified
844 801 ims_value = self.request.headers.get("If-Modified-Since")
845 802 if ims_value is not None:
846 803 date_tuple = email.utils.parsedate(ims_value)
847 804 if_since = datetime.datetime(*date_tuple[:6])
848 805 if if_since >= modified:
849 806 self.set_status(304)
850 807 return
851 808
852 809 with open(abspath, "rb") as file:
853 810 data = file.read()
854 811 hasher = hashlib.sha1()
855 812 hasher.update(data)
856 813 self.set_header("Etag", '"%s"' % hasher.hexdigest())
857 814 if include_body:
858 815 self.write(data)
859 816 else:
860 817 assert self.request.method == "HEAD"
861 818 self.set_header("Content-Length", len(data))
862 819
863 820 @classmethod
864 821 def get_version(cls, settings, path):
865 822 """Generate the version string to be used in static URLs.
866 823
867 824 This method may be overridden in subclasses (but note that it
868 825 is a class method rather than a static method). The default
869 826 implementation uses a hash of the file's contents.
870 827
871 828 ``settings`` is the `Application.settings` dictionary and ``path``
872 829 is the relative location of the requested asset on the filesystem.
873 830 The returned value should be a string, or ``None`` if no version
874 831 could be determined.
875 832 """
876 833 # begin subclass override:
877 834 static_paths = settings['static_path']
878 835 if isinstance(static_paths, basestring):
879 836 static_paths = [static_paths]
880 837 roots = tuple(
881 838 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
882 839 )
883 840
884 841 try:
885 842 abs_path = filefind(path, roots)
886 843 except IOError:
887 844 logging.error("Could not find static file %r", path)
888 845 return None
889 846
890 847 # end subclass override
891 848
892 849 with cls._lock:
893 850 hashes = cls._static_hashes
894 851 if abs_path not in hashes:
895 852 try:
896 853 f = open(abs_path, "rb")
897 854 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
898 855 f.close()
899 856 except Exception:
900 857 logging.error("Could not open static file %r", path)
901 858 hashes[abs_path] = None
902 859 hsh = hashes.get(abs_path)
903 860 if hsh:
904 861 return hsh[:5]
905 862 return None
906 863
907 864
908 865 def parse_url_path(self, url_path):
909 866 """Converts a static URL path into a filesystem path.
910 867
911 868 ``url_path`` is the path component of the URL with
912 869 ``static_url_prefix`` removed. The return value should be
913 870 filesystem path relative to ``static_path``.
914 871 """
915 872 if os.path.sep != "/":
916 873 url_path = url_path.replace("/", os.path.sep)
917 874 return url_path
918 875
919 876
@@ -1,133 +1,115
1 1 """A kernel manager relating notebooks and kernels
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Float, Integer,
23 Dict, List, Unicode, Integer,
24 24 )
25
25 26 #-----------------------------------------------------------------------------
26 27 # Classes
27 28 #-----------------------------------------------------------------------------
28 29
29 30
30 31 class MappingKernelManager(MultiKernelManager):
31 32 """A KernelManager that handles notebok mapping and HTTP error handling"""
32 33
33 kernel_argv = List(Unicode)
34 def _kernel_manager_class_default(self):
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
34 36
35 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
36 first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")
37 kernel_argv = List(Unicode)
37 38
38 39 max_msg_size = Integer(65536, config=True, help="""
39 40 The max raw message size accepted from the browser
40 41 over a WebSocket connection.
41 42 """)
42 43
43 44 _notebook_mapping = Dict()
44 45
45 46 #-------------------------------------------------------------------------
46 47 # Methods for managing kernels and sessions
47 48 #-------------------------------------------------------------------------
48 49
49 50 def kernel_for_notebook(self, notebook_id):
50 51 """Return the kernel_id for a notebook_id or None."""
51 52 return self._notebook_mapping.get(notebook_id)
52 53
53 54 def set_kernel_for_notebook(self, notebook_id, kernel_id):
54 55 """Associate a notebook with a kernel."""
55 56 if notebook_id is not None:
56 57 self._notebook_mapping[notebook_id] = kernel_id
57 58
58 59 def notebook_for_kernel(self, kernel_id):
59 60 """Return the notebook_id for a kernel_id or None."""
60 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
61 if len(notebook_ids) == 1:
62 return notebook_ids[0]
63 else:
61 for notebook_id, kid in self._notebook_mapping.iteritems():
62 if kernel_id == kid:
63 return notebook_id
64 64 return None
65 65
66 66 def delete_mapping_for_kernel(self, kernel_id):
67 67 """Remove the kernel/notebook mapping for kernel_id."""
68 68 notebook_id = self.notebook_for_kernel(kernel_id)
69 69 if notebook_id is not None:
70 70 del self._notebook_mapping[notebook_id]
71 71
72 def _handle_kernel_died(self, kernel_id):
73 """notice that a kernel died"""
74 self.log.warn("Kernel %s died, removing from map.", kernel_id)
75 self.delete_mapping_for_kernel(kernel_id)
76 self.remove_kernel(kernel_id, now=True)
77
72 78 def start_kernel(self, notebook_id=None, **kwargs):
73 """Start a kernel for a notebok an return its kernel_id.
79 """Start a kernel for a notebook an return its kernel_id.
74 80
75 81 Parameters
76 82 ----------
77 83 notebook_id : uuid
78 84 The uuid of the notebook to associate the new kernel with. If this
79 85 is not None, this kernel will be persistent whenever the notebook
80 86 requests a kernel.
81 87 """
82 88 kernel_id = self.kernel_for_notebook(notebook_id)
83 89 if kernel_id is None:
84 90 kwargs['extra_arguments'] = self.kernel_argv
85 91 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
86 92 self.set_kernel_for_notebook(notebook_id, kernel_id)
87 93 self.log.info("Kernel started: %s" % kernel_id)
88 94 self.log.debug("Kernel args: %r" % kwargs)
95 # register callback for failed auto-restart
96 self.add_restart_callback(kernel_id,
97 lambda : self._handle_kernel_died(kernel_id),
98 'dead',
99 )
89 100 else:
90 101 self.log.info("Using existing kernel: %s" % kernel_id)
102
91 103 return kernel_id
92 104
93 105 def shutdown_kernel(self, kernel_id, now=False):
94 """Shutdown a kernel and remove its notebook association."""
95 self._check_kernel_id(kernel_id)
96 super(MappingKernelManager, self).shutdown_kernel(
97 kernel_id, now=now
98 )
106 """Shutdown a kernel by kernel_id"""
107 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
99 108 self.delete_mapping_for_kernel(kernel_id)
100 self.log.info("Kernel shutdown: %s" % kernel_id)
101
102 def interrupt_kernel(self, kernel_id):
103 """Interrupt a kernel."""
104 self._check_kernel_id(kernel_id)
105 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
106 self.log.info("Kernel interrupted: %s" % kernel_id)
107
108 def restart_kernel(self, kernel_id):
109 """Restart a kernel while keeping clients connected."""
110 self._check_kernel_id(kernel_id)
111 super(MappingKernelManager, self).restart_kernel(kernel_id)
112 self.log.info("Kernel restarted: %s" % kernel_id)
113
114 def create_iopub_stream(self, kernel_id):
115 """Create a new iopub stream."""
116 self._check_kernel_id(kernel_id)
117 return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
118
119 def create_shell_stream(self, kernel_id):
120 """Create a new shell stream."""
121 self._check_kernel_id(kernel_id)
122 return super(MappingKernelManager, self).create_shell_stream(kernel_id)
123
124 def create_hb_stream(self, kernel_id):
125 """Create a new hb stream."""
126 self._check_kernel_id(kernel_id)
127 return super(MappingKernelManager, self).create_hb_stream(kernel_id)
128 109
110 # override _check_kernel_id to raise 404 instead of KeyError
129 111 def _check_kernel_id(self, kernel_id):
130 112 """Check a that a kernel_id exists and raise 404 if not."""
131 113 if kernel_id not in self:
132 114 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
133 115
@@ -1,439 +1,441
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Kernel
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 // Initialization and connection.
23 23 /**
24 24 * A Kernel Class to communicate with the Python kernel
25 25 * @Class Kernel
26 26 */
27 27 var Kernel = function (base_url) {
28 28 this.kernel_id = null;
29 29 this.shell_channel = null;
30 30 this.iopub_channel = null;
31 31 this.base_url = base_url;
32 32 this.running = false;
33 33 this.username = "username";
34 34 this.session_id = utils.uuid();
35 35 this._msg_callbacks = {};
36 36
37 37 if (typeof(WebSocket) !== 'undefined') {
38 38 this.WebSocket = WebSocket;
39 39 } else if (typeof(MozWebSocket) !== 'undefined') {
40 40 this.WebSocket = MozWebSocket;
41 41 } else {
42 42 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
43 43 };
44 44 };
45 45
46 46
47 47 Kernel.prototype._get_msg = function (msg_type, content) {
48 48 var msg = {
49 49 header : {
50 50 msg_id : utils.uuid(),
51 51 username : this.username,
52 52 session : this.session_id,
53 53 msg_type : msg_type
54 54 },
55 55 metadata : {},
56 56 content : content,
57 57 parent_header : {}
58 58 };
59 59 return msg;
60 60 };
61 61
62 62 /**
63 63 * Start the Python kernel
64 64 * @method start
65 65 */
66 66 Kernel.prototype.start = function (notebook_id) {
67 67 var that = this;
68 68 if (!this.running) {
69 69 var qs = $.param({notebook:notebook_id});
70 70 var url = this.base_url + '?' + qs;
71 71 $.post(url,
72 72 $.proxy(that._kernel_started,that),
73 73 'json'
74 74 );
75 75 };
76 76 };
77 77
78 78 /**
79 79 * Restart the python kernel.
80 80 *
81 81 * Emit a 'status_restarting.Kernel' event with
82 82 * the current object as parameter
83 83 *
84 84 * @method restart
85 85 */
86 86 Kernel.prototype.restart = function () {
87 87 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
88 88 var that = this;
89 89 if (this.running) {
90 90 this.stop_channels();
91 91 var url = this.kernel_url + "/restart";
92 92 $.post(url,
93 93 $.proxy(that._kernel_started, that),
94 94 'json'
95 95 );
96 96 };
97 97 };
98 98
99 99
100 100 Kernel.prototype._kernel_started = function (json) {
101 101 console.log("Kernel started: ", json.kernel_id);
102 102 this.running = true;
103 103 this.kernel_id = json.kernel_id;
104 104 this.ws_url = json.ws_url;
105 105 this.kernel_url = this.base_url + "/" + this.kernel_id;
106 106 this.start_channels();
107 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
108 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
109 107 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
110 108 };
111 109
112 110
113 111 Kernel.prototype._websocket_closed = function(ws_url, early) {
114 112 this.stop_channels();
115 113 $([IPython.events]).trigger('websocket_closed.Kernel',
116 114 {ws_url: ws_url, kernel: this, early: early}
117 115 );
118 116 };
119 117
120 118 /**
121 119 * Start the `shell`and `iopub` channels.
122 120 * Will stop and restart them if they already exist.
123 121 *
124 122 * @method start_channels
125 123 */
126 124 Kernel.prototype.start_channels = function () {
127 125 var that = this;
128 126 this.stop_channels();
129 127 var ws_url = this.ws_url + this.kernel_url;
130 128 console.log("Starting WebSockets:", ws_url);
131 129 this.shell_channel = new this.WebSocket(ws_url + "/shell");
132 130 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
133 131 send_cookie = function(){
134 132 this.send(document.cookie);
135 133 };
136 134 var already_called_onclose = false; // only alert once
137 135 var ws_closed_early = function(evt){
138 136 if (already_called_onclose){
139 137 return;
140 138 }
141 139 already_called_onclose = true;
142 140 if ( ! evt.wasClean ){
143 141 that._websocket_closed(ws_url, true);
144 142 }
145 143 };
146 144 var ws_closed_late = function(evt){
147 145 if (already_called_onclose){
148 146 return;
149 147 }
150 148 already_called_onclose = true;
151 149 if ( ! evt.wasClean ){
152 150 that._websocket_closed(ws_url, false);
153 151 }
154 152 };
155 153 this.shell_channel.onopen = send_cookie;
156 154 this.shell_channel.onclose = ws_closed_early;
157 155 this.iopub_channel.onopen = send_cookie;
158 156 this.iopub_channel.onclose = ws_closed_early;
159 157 // switch from early-close to late-close message after 1s
160 158 setTimeout(function() {
161 159 if (that.shell_channel !== null) {
162 160 that.shell_channel.onclose = ws_closed_late;
163 161 }
164 162 if (that.iopub_channel !== null) {
165 163 that.iopub_channel.onclose = ws_closed_late;
166 164 }
167 165 }, 1000);
166 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
167 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
168 168 };
169 169
170 170 /**
171 171 * Start the `shell`and `iopub` channels.
172 172 * @method stop_channels
173 173 */
174 174 Kernel.prototype.stop_channels = function () {
175 175 if (this.shell_channel !== null) {
176 176 this.shell_channel.onclose = function (evt) {};
177 177 this.shell_channel.close();
178 178 this.shell_channel = null;
179 179 };
180 180 if (this.iopub_channel !== null) {
181 181 this.iopub_channel.onclose = function (evt) {};
182 182 this.iopub_channel.close();
183 183 this.iopub_channel = null;
184 184 };
185 185 };
186 186
187 187 // Main public methods.
188 188
189 189 /**
190 190 * Get info on object asynchronoulsy
191 191 *
192 192 * @async
193 193 * @param objname {string}
194 194 * @param callback {dict}
195 195 * @method object_info_request
196 196 *
197 197 * @example
198 198 *
199 199 * When calling this method pass a callbacks structure of the form:
200 200 *
201 201 * callbacks = {
202 202 * 'object_info_reply': object_info_reply_callback
203 203 * }
204 204 *
205 205 * The `object_info_reply_callback` will be passed the content object of the
206 206 *
207 207 * `object_into_reply` message documented in
208 208 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
209 209 */
210 210 Kernel.prototype.object_info_request = function (objname, callbacks) {
211 211 if(typeof(objname)!=null && objname!=null)
212 212 {
213 213 var content = {
214 214 oname : objname.toString(),
215 215 };
216 216 var msg = this._get_msg("object_info_request", content);
217 217 this.shell_channel.send(JSON.stringify(msg));
218 218 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
219 219 return msg.header.msg_id;
220 220 }
221 221 return;
222 222 }
223 223
224 224 /**
225 225 * Execute given code into kernel, and pass result to callback.
226 226 *
227 227 * @async
228 228 * @method execute
229 229 * @param {string} code
230 230 * @param callback {Object} With the following keys
231 231 * @param callback.'execute_reply' {function}
232 232 * @param callback.'output' {function}
233 233 * @param callback.'clear_output' {function}
234 234 * @param callback.'set_next_input' {function}
235 235 * @param {object} [options]
236 236 * @param [options.silent=false] {Boolean}
237 237 * @param [options.user_expressions=empty_dict] {Dict}
238 238 * @param [options.user_variables=empty_list] {List od Strings}
239 239 * @param [options.allow_stdin=false] {Boolean} true|false
240 240 *
241 241 * @example
242 242 *
243 243 * The options object should contain the options for the execute call. Its default
244 244 * values are:
245 245 *
246 246 * options = {
247 247 * silent : true,
248 248 * user_variables : [],
249 249 * user_expressions : {},
250 250 * allow_stdin : false
251 251 * }
252 252 *
253 253 * When calling this method pass a callbacks structure of the form:
254 254 *
255 255 * callbacks = {
256 256 * 'execute_reply': execute_reply_callback,
257 257 * 'output': output_callback,
258 258 * 'clear_output': clear_output_callback,
259 259 * 'set_next_input': set_next_input_callback
260 260 * }
261 261 *
262 262 * The `execute_reply_callback` will be passed the content and metadata
263 263 * objects of the `execute_reply` message documented
264 264 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
265 265 *
266 266 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
267 267 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
268 268 * output:
269 269 *
270 270 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
271 271 *
272 272 * The `clear_output_callback` will be passed a content object that contains
273 273 * stdout, stderr and other fields that are booleans, as well as the metadata object.
274 274 *
275 275 * The `set_next_input_callback` will be passed the text that should become the next
276 276 * input cell.
277 277 */
278 278 Kernel.prototype.execute = function (code, callbacks, options) {
279 279
280 280 var content = {
281 281 code : code,
282 282 silent : true,
283 283 user_variables : [],
284 284 user_expressions : {},
285 285 allow_stdin : false
286 286 };
287 287 $.extend(true, content, options)
288 288 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
289 289 var msg = this._get_msg("execute_request", content);
290 290 this.shell_channel.send(JSON.stringify(msg));
291 291 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
292 292 return msg.header.msg_id;
293 293 };
294 294
295 295 /**
296 296 * When calling this method pass a callbacks structure of the form:
297 297 *
298 298 * callbacks = {
299 299 * 'complete_reply': complete_reply_callback
300 300 * }
301 301 *
302 302 * The `complete_reply_callback` will be passed the content object of the
303 303 * `complete_reply` message documented
304 304 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
305 305 *
306 306 * @method complete
307 307 * @param line {integer}
308 308 * @param cursor_pos {integer}
309 309 * @param {dict} callbacks
310 310 * @param callbacks.complete_reply {function} `complete_reply_callback`
311 311 *
312 312 */
313 313 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
314 314 callbacks = callbacks || {};
315 315 var content = {
316 316 text : '',
317 317 line : line,
318 318 cursor_pos : cursor_pos
319 319 };
320 320 var msg = this._get_msg("complete_request", content);
321 321 this.shell_channel.send(JSON.stringify(msg));
322 322 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
323 323 return msg.header.msg_id;
324 324 };
325 325
326 326
327 327 Kernel.prototype.interrupt = function () {
328 328 if (this.running) {
329 329 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
330 330 $.post(this.kernel_url + "/interrupt");
331 331 };
332 332 };
333 333
334 334
335 335 Kernel.prototype.kill = function () {
336 336 if (this.running) {
337 337 this.running = false;
338 338 var settings = {
339 339 cache : false,
340 340 type : "DELETE"
341 341 };
342 342 $.ajax(this.kernel_url, settings);
343 343 };
344 344 };
345 345
346 346
347 347 // Reply handlers.
348 348
349 349 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
350 350 var callbacks = this._msg_callbacks[msg_id];
351 351 return callbacks;
352 352 };
353 353
354 354
355 355 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
356 356 this._msg_callbacks[msg_id] = callbacks || {};
357 357 }
358 358
359 359
360 360 Kernel.prototype._handle_shell_reply = function (e) {
361 361 var reply = $.parseJSON(e.data);
362 362 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
363 363 var header = reply.header;
364 364 var content = reply.content;
365 365 var metadata = reply.metadata;
366 366 var msg_type = header.msg_type;
367 367 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
368 368 if (callbacks !== undefined) {
369 369 var cb = callbacks[msg_type];
370 370 if (cb !== undefined) {
371 371 cb(content, metadata);
372 372 }
373 373 };
374 374
375 375 if (content.payload !== undefined) {
376 376 var payload = content.payload || [];
377 377 this._handle_payload(callbacks, payload);
378 378 }
379 379 };
380 380
381 381
382 382 Kernel.prototype._handle_payload = function (callbacks, payload) {
383 383 var l = payload.length;
384 384 // Payloads are handled by triggering events because we don't want the Kernel
385 385 // to depend on the Notebook or Pager classes.
386 386 for (var i=0; i<l; i++) {
387 387 if (payload[i].source === 'IPython.kernel.zmq.page.page') {
388 388 var data = {'text':payload[i].text}
389 389 $([IPython.events]).trigger('open_with_text.Pager', data);
390 390 } else if (payload[i].source === 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
391 391 if (callbacks.set_next_input !== undefined) {
392 392 callbacks.set_next_input(payload[i].text)
393 393 }
394 394 }
395 395 };
396 396 };
397 397
398 398
399 399 Kernel.prototype._handle_iopub_reply = function (e) {
400 400 var reply = $.parseJSON(e.data);
401 401 var content = reply.content;
402 402 var msg_type = reply.header.msg_type;
403 403 var metadata = reply.metadata;
404 404 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
405 405 if (msg_type !== 'status' && callbacks === undefined) {
406 406 // Message not from one of this notebook's cells and there are no
407 407 // callbacks to handle it.
408 408 return;
409 409 }
410 410 var output_types = ['stream','display_data','pyout','pyerr'];
411 411 if (output_types.indexOf(msg_type) >= 0) {
412 412 var cb = callbacks['output'];
413 413 if (cb !== undefined) {
414 414 cb(msg_type, content, metadata);
415 415 }
416 416 } else if (msg_type === 'status') {
417 417 if (content.execution_state === 'busy') {
418 418 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
419 419 } else if (content.execution_state === 'idle') {
420 420 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
421 } else if (content.execution_state === 'restarting') {
422 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
421 423 } else if (content.execution_state === 'dead') {
422 424 this.stop_channels();
423 425 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
424 426 };
425 427 } else if (msg_type === 'clear_output') {
426 428 var cb = callbacks['clear_output'];
427 429 if (cb !== undefined) {
428 430 cb(content, metadata);
429 431 }
430 432 };
431 433 };
432 434
433 435
434 436 IPython.Kernel = Kernel;
435 437
436 438 return IPython;
437 439
438 440 }(IPython));
439 441
@@ -1,187 +1,188
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notification widget
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14 var utils = IPython.utils;
15 15
16 16
17 17 var NotificationArea = function (selector) {
18 18 this.selector = selector;
19 19 if (this.selector !== undefined) {
20 20 this.element = $(selector);
21 21 }
22 22 this.widget_dict = {};
23 23 };
24 24
25 25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 26 var uuid = utils.uuid();
27 27 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 29 var tdiv = $('<div>')
30 30 .attr('id',uuid)
31 31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 32 .addClass('border-box-sizing')
33 33 .addClass(css_class)
34 34 .hide()
35 35 .text(msg);
36 36
37 37 $(this.selector).append(tdiv);
38 38 var tmout = Math.max(1500,(timeout||1500));
39 39 tdiv.fadeIn(100);
40 40
41 41 setTimeout(function () {
42 42 tdiv.fadeOut(100, function () {tdiv.remove();});
43 43 }, tmout);
44 44 };
45 45
46 46 NotificationArea.prototype.widget = function(name) {
47 47 if(this.widget_dict[name] == undefined) {
48 48 return this.new_notification_widget(name);
49 49 }
50 50 return this.get_widget(name);
51 51 };
52 52
53 53 NotificationArea.prototype.get_widget = function(name) {
54 54 if(this.widget_dict[name] == undefined) {
55 55 throw('no widgets with this name');
56 56 }
57 57 return this.widget_dict[name];
58 58 };
59 59
60 60 NotificationArea.prototype.new_notification_widget = function(name) {
61 61 if(this.widget_dict[name] != undefined) {
62 62 throw('widget with that name already exists ! ');
63 63 }
64 64 var div = $('<div/>').attr('id','notification_'+name);
65 65 $(this.selector).append(div);
66 66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
67 67 return this.widget_dict[name];
68 68 };
69 69
70 70 NotificationArea.prototype.init_notification_widgets = function() {
71 71 var knw = this.new_notification_widget('kernel');
72 72
73 73 // Kernel events
74 74 $([IPython.events]).on('status_idle.Kernel',function () {
75 75 IPython.save_widget.update_document_title();
76 76 knw.set_message('Kernel Idle',200);
77 77 }
78 78 );
79 79
80 80 $([IPython.events]).on('status_busy.Kernel',function () {
81 81 window.document.title='(Busy) '+window.document.title;
82 82 knw.set_message("Kernel busy");
83 83 });
84 84
85 85 $([IPython.events]).on('status_restarting.Kernel',function () {
86 86 IPython.save_widget.update_document_title();
87 knw.set_message("Restarting kernel",1000);
87 knw.set_message("Restarting kernel", 2000);
88 88 });
89 89
90 90 $([IPython.events]).on('status_interrupting.Kernel',function () {
91 91 knw.set_message("Interrupting kernel");
92 92 });
93 93
94 94 $([IPython.events]).on('status_dead.Kernel',function () {
95 95 var dialog = $('<div/>');
96 dialog.html('The kernel has died, would you like to restart it?' +
97 ' If you do not restart the kernel, you will be able to save' +
98 ' the notebook, but running code will not work until the notebook' +
96 dialog.html('The kernel has died, and the automatic restart has failed.' +
97 ' It is possible the kernel cannot be restarted.' +
98 ' If you are not able to restart the kernel, you will still be able to save' +
99 ' the notebook, but running code will no longer work until the notebook' +
99 100 ' is reopened.'
100 101 );
101 102 $(document).append(dialog);
102 103 dialog.dialog({
103 104 resizable: false,
104 105 modal: true,
105 106 title: "Dead kernel",
106 107 close: function(event, ui) {$(this).dialog('destroy').remove();},
107 108 buttons : {
108 "Restart": function () {
109 "Manual Restart": function () {
109 110 $([IPython.events]).trigger('status_restarting.Kernel');
110 111 IPython.notebook.start_kernel();
111 112 $(this).dialog('close');
112 113 },
113 114 "Don't restart": function () {
114 115 $(this).dialog('close');
115 116 }
116 117 }
117 118 });
118 119 });
119 120
120 121 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
121 122 var kernel = data.kernel;
122 123 var ws_url = data.ws_url;
123 124 var early = data.early;
124 125 var msg;
125 126 if (!early) {
126 127 knw.set_message('Reconnecting WebSockets', 1000);
127 128 setTimeout(function () {
128 129 kernel.start_channels();
129 130 }, 5000);
130 131 return;
131 132 }
132 133 console.log('WebSocket connection failed: ', ws_url)
133 134 msg = "A WebSocket connection to could not be established." +
134 135 " You will NOT be able to run code. Check your" +
135 136 " network connection or notebook server configuration.";
136 137 var dialog = $('<div/>');
137 138 dialog.html(msg);
138 139 $(document).append(dialog);
139 140 dialog.dialog({
140 141 resizable: false,
141 142 modal: true,
142 143 title: "WebSocket connection failed",
143 144 closeText: "",
144 145 close: function(event, ui) {$(this).dialog('destroy').remove();},
145 146 buttons : {
146 147 "OK": function () {
147 148 $(this).dialog('close');
148 149 },
149 150 "Reconnect": function () {
150 151 knw.set_message('Reconnecting WebSockets', 1000);
151 152 setTimeout(function () {
152 153 kernel.start_channels();
153 154 }, 5000);
154 155 $(this).dialog('close');
155 156 }
156 157 }
157 158 });
158 159 });
159 160
160 161
161 162 var nnw = this.new_notification_widget('notebook');
162 163
163 164 // Notebook events
164 165 $([IPython.events]).on('notebook_loading.Notebook', function () {
165 166 nnw.set_message("Loading notebook",500);
166 167 });
167 168 $([IPython.events]).on('notebook_loaded.Notebook', function () {
168 169 nnw.set_message("Notebook loaded",500);
169 170 });
170 171 $([IPython.events]).on('notebook_saving.Notebook', function () {
171 172 nnw.set_message("Saving notebook",500);
172 173 });
173 174 $([IPython.events]).on('notebook_saved.Notebook', function () {
174 175 nnw.set_message("Notebook saved",2000);
175 176 });
176 177 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
177 178 nnw.set_message("Notebook save failed");
178 179 });
179 180
180 181 };
181 182
182 183 IPython.NotificationArea = NotificationArea;
183 184
184 185 return IPython;
185 186
186 187 }(IPython));
187 188
@@ -1,121 +1,150
1 1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 2 """
3 3
4 4 class BaseFrontendMixin(object):
5 5 """ A mix-in class for implementing Qt frontends.
6 6
7 7 To handle messages of a particular type, frontends need only define an
8 8 appropriate handler method. For example, to handle 'stream' messaged, define
9 9 a '_handle_stream(msg)' method.
10 10 """
11 11
12 12 #---------------------------------------------------------------------------
13 13 # 'BaseFrontendMixin' concrete interface
14 14 #---------------------------------------------------------------------------
15
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
15 _kernel_client = None
16 _kernel_manager = None
17
18 @property
19 def kernel_client(self):
20 """Returns the current kernel client."""
21 return self._kernel_client
22
23 @kernel_client.setter
24 def kernel_client(self, kernel_client):
25 """Disconnect from the current kernel client (if any) and set a new
26 kernel client.
18 27 """
19 return self._kernel_manager
20
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
24 """
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
27 if old_manager is not None:
28 old_manager.started_kernel.disconnect(self._started_kernel)
29 old_manager.started_channels.disconnect(self._started_channels)
30 old_manager.stopped_channels.disconnect(self._stopped_channels)
31
32 # Disconnect the old kernel manager's channels.
33 old_manager.iopub_channel.message_received.disconnect(self._dispatch)
34 old_manager.shell_channel.message_received.disconnect(self._dispatch)
35 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
36 old_manager.hb_channel.kernel_died.disconnect(
28 # Disconnect the old kernel client, if necessary.
29 old_client = self._kernel_client
30 if old_client is not None:
31 old_client.started_channels.disconnect(self._started_channels)
32 old_client.stopped_channels.disconnect(self._stopped_channels)
33
34 # Disconnect the old kernel client's channels.
35 old_client.iopub_channel.message_received.disconnect(self._dispatch)
36 old_client.shell_channel.message_received.disconnect(self._dispatch)
37 old_client.stdin_channel.message_received.disconnect(self._dispatch)
38 old_client.hb_channel.kernel_died.disconnect(
37 39 self._handle_kernel_died)
38 40
39 # Handle the case where the old kernel manager is still listening.
40 if old_manager.channels_running:
41 # Handle the case where the old kernel client is still listening.
42 if old_client.channels_running:
41 43 self._stopped_channels()
42 44
43 # Set the new kernel manager.
44 self._kernel_manager = kernel_manager
45 if kernel_manager is None:
45 # Set the new kernel client.
46 self._kernel_client = kernel_client
47 if kernel_client is None:
46 48 return
47 49
48 # Connect the new kernel manager.
49 kernel_manager.started_kernel.connect(self._started_kernel)
50 kernel_manager.started_channels.connect(self._started_channels)
51 kernel_manager.stopped_channels.connect(self._stopped_channels)
50 # Connect the new kernel client.
51 kernel_client.started_channels.connect(self._started_channels)
52 kernel_client.stopped_channels.connect(self._stopped_channels)
52 53
53 # Connect the new kernel manager's channels.
54 kernel_manager.iopub_channel.message_received.connect(self._dispatch)
55 kernel_manager.shell_channel.message_received.connect(self._dispatch)
56 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
57 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
54 # Connect the new kernel client's channels.
55 kernel_client.iopub_channel.message_received.connect(self._dispatch)
56 kernel_client.shell_channel.message_received.connect(self._dispatch)
57 kernel_client.stdin_channel.message_received.connect(self._dispatch)
58 # hb_channel
59 kernel_client.hb_channel.kernel_died.connect(self._handle_kernel_died)
58 60
59 # Handle the case where the kernel manager started channels before
61 # Handle the case where the kernel client started channels before
60 62 # we connected.
61 if kernel_manager.channels_running:
63 if kernel_client.channels_running:
62 64 self._started_channels()
63 65
64 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
66 @property
67 def kernel_manager(self):
68 """The kernel manager, if any"""
69 return self._kernel_manager
70
71 @kernel_manager.setter
72 def kernel_manager(self, kernel_manager):
73 old_man = self._kernel_manager
74 if old_man is not None:
75 old_man.kernel_restarted.disconnect(self._handle_kernel_restarted)
76
77 self._kernel_manager = kernel_manager
78 if kernel_manager is None:
79 return
80
81 kernel_manager.kernel_restarted.connect(self._handle_kernel_restarted)
65 82
66 83 #---------------------------------------------------------------------------
67 84 # 'BaseFrontendMixin' abstract interface
68 85 #---------------------------------------------------------------------------
69 86
70 87 def _handle_kernel_died(self, since_last_heartbeat):
71 88 """ This is called when the ``kernel_died`` signal is emitted.
72 89
73 90 This method is called when the kernel heartbeat has not been
74 active for a certain amount of time. The typical action will be to
75 give the user the option of restarting the kernel.
91 active for a certain amount of time.
92 This is a strictly passive notification -
93 the kernel is likely being restarted by its KernelManager.
76 94
77 95 Parameters
78 96 ----------
79 97 since_last_heartbeat : float
80 98 The time since the heartbeat was last received.
81 99 """
82 100
101 def _handle_kernel_restarted(self):
102 """ This is called when the ``kernel_restarted`` signal is emitted.
103
104 This method is called when the kernel has been restarted by the
105 autorestart mechanism.
106
107 Parameters
108 ----------
109 since_last_heartbeat : float
110 The time since the heartbeat was last received.
111 """
83 112 def _started_kernel(self):
84 113 """Called when the KernelManager starts (or restarts) the kernel subprocess.
85 114 Channels may or may not be running at this point.
86 115 """
87 116
88 117 def _started_channels(self):
89 118 """ Called when the KernelManager channels have started listening or
90 119 when the frontend is assigned an already listening KernelManager.
91 120 """
92 121
93 122 def _stopped_channels(self):
94 123 """ Called when the KernelManager channels have stopped listening or
95 124 when a listening KernelManager is removed from the frontend.
96 125 """
97 126
98 127 #---------------------------------------------------------------------------
99 128 # 'BaseFrontendMixin' protected interface
100 129 #---------------------------------------------------------------------------
101 130
102 131 def _dispatch(self, msg):
103 132 """ Calls the frontend handler associated with the message type of the
104 133 given message.
105 134 """
106 135 msg_type = msg['header']['msg_type']
107 136 handler = getattr(self, '_handle_' + msg_type, None)
108 137 if handler:
109 138 handler(msg)
110 139
111 140 def _is_from_this_session(self, msg):
112 141 """ Returns whether a reply from the kernel originated from a request
113 142 from this frontend.
114 143 """
115 session = self._kernel_manager.session.session
144 session = self._kernel_client.session.session
116 145 parent = msg['parent_header']
117 146 if not parent:
118 147 # if the message has no parent, assume it is meant for all frontends
119 148 return True
120 149 else:
121 150 return parent.get('session') == session
@@ -1,32 +1,37
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelClient that provides signals and slots.
2 2 """
3 3
4 # Local imports.
4 # Local imports
5 5 from IPython.utils.traitlets import Type
6 from IPython.kernel.kernelmanager import ShellChannel, IOPubChannel, \
7 StdInChannel, HBChannel, KernelManager
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
6 from IPython.kernel.channels import (
7 ShellChannel, IOPubChannel, StdInChannel, HBChannel
8 )
9 from IPython.kernel import KernelClient
10 10
11 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin,
14 QtKernelClientMixin
15 )
11 16
12 17 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 18 pass
14 19
15 20 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
16 21 pass
17 22
18 23 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
19 24 pass
20 25
21 26 class QtHBChannel(QtHBChannelMixin, HBChannel):
22 27 pass
23 28
24 29
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
26 """ A KernelManager that provides signals and slots.
30 class QtKernelClient(QtKernelClientMixin, KernelClient):
31 """ A KernelClient that provides signals and slots.
27 32 """
28 33
29 34 iopub_channel_class = Type(QtIOPubChannel)
30 35 shell_channel_class = Type(QtShellChannel)
31 36 stdin_channel_class = Type(QtStdInChannel)
32 37 hb_channel_class = Type(QtHBChannel)
@@ -1,771 +1,785
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 6 import time
7 7 import uuid
8 8
9 9 # System library imports
10 10 from pygments.lexers import PythonLexer
11 11 from IPython.external import qt
12 12 from IPython.external.qt import QtCore, QtGui
13 13
14 14 # Local imports
15 15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 16 from IPython.core.inputtransformer import classic_prompt
17 17 from IPython.core.oinspect import call_tip
18 18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
19 19 from IPython.utils.traitlets import Bool, Instance, Unicode
20 20 from bracket_matcher import BracketMatcher
21 21 from call_tip_widget import CallTipWidget
22 22 from completion_lexer import CompletionLexer
23 23 from history_console_widget import HistoryConsoleWidget
24 24 from pygments_highlighter import PygmentsHighlighter
25 25
26 26
27 27 class FrontendHighlighter(PygmentsHighlighter):
28 28 """ A PygmentsHighlighter that understands and ignores prompts.
29 29 """
30 30
31 31 def __init__(self, frontend):
32 32 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 33 self._current_offset = 0
34 34 self._frontend = frontend
35 35 self.highlighting_on = False
36 36
37 37 def highlightBlock(self, string):
38 38 """ Highlight a block of text. Reimplemented to highlight selectively.
39 39 """
40 40 if not self.highlighting_on:
41 41 return
42 42
43 43 # The input to this function is a unicode string that may contain
44 44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 45 # the string as plain text so we can compare it.
46 46 current_block = self.currentBlock()
47 47 string = self._frontend._get_block_plain_text(current_block)
48 48
49 49 # Decide whether to check for the regular or continuation prompt.
50 50 if current_block.contains(self._frontend._prompt_pos):
51 51 prompt = self._frontend._prompt
52 52 else:
53 53 prompt = self._frontend._continuation_prompt
54 54
55 55 # Only highlight if we can identify a prompt, but make sure not to
56 56 # highlight the prompt.
57 57 if string.startswith(prompt):
58 58 self._current_offset = len(prompt)
59 59 string = string[len(prompt):]
60 60 super(FrontendHighlighter, self).highlightBlock(string)
61 61
62 62 def rehighlightBlock(self, block):
63 63 """ Reimplemented to temporarily enable highlighting if disabled.
64 64 """
65 65 old = self.highlighting_on
66 66 self.highlighting_on = True
67 67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 68 self.highlighting_on = old
69 69
70 70 def setFormat(self, start, count, format):
71 71 """ Reimplemented to highlight selectively.
72 72 """
73 73 start += self._current_offset
74 74 super(FrontendHighlighter, self).setFormat(start, count, format)
75 75
76 76
77 77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 78 """ A Qt frontend for a generic Python kernel.
79 79 """
80 80
81 81 # The text to show when the kernel is (re)started.
82 82 banner = Unicode()
83 83
84 84 # An option and corresponding signal for overriding the default kernel
85 85 # interrupt behavior.
86 86 custom_interrupt = Bool(False)
87 87 custom_interrupt_requested = QtCore.Signal()
88 88
89 89 # An option and corresponding signals for overriding the default kernel
90 90 # restart behavior.
91 91 custom_restart = Bool(False)
92 92 custom_restart_kernel_died = QtCore.Signal(float)
93 93 custom_restart_requested = QtCore.Signal()
94 94
95 95 # Whether to automatically show calltips on open-parentheses.
96 96 enable_calltips = Bool(True, config=True,
97 97 help="Whether to draw information calltips on open-parentheses.")
98 98
99 99 clear_on_kernel_restart = Bool(True, config=True,
100 100 help="Whether to clear the console when the kernel is restarted")
101 101
102 102 confirm_restart = Bool(True, config=True,
103 103 help="Whether to ask for user confirmation when restarting kernel")
104 104
105 105 # Emitted when a user visible 'execute_request' has been submitted to the
106 106 # kernel from the FrontendWidget. Contains the code to be executed.
107 107 executing = QtCore.Signal(object)
108 108
109 109 # Emitted when a user-visible 'execute_reply' has been received from the
110 110 # kernel and processed by the FrontendWidget. Contains the response message.
111 111 executed = QtCore.Signal(object)
112 112
113 113 # Emitted when an exit request has been received from the kernel.
114 114 exit_requested = QtCore.Signal(object)
115 115
116 116 # Protected class variables.
117 117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 118 logical_line_transforms=[],
119 119 python_line_transforms=[],
120 120 )
121 121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
122 122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
123 123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
124 124 _input_splitter_class = InputSplitter
125 125 _local_kernel = False
126 126 _highlighter = Instance(FrontendHighlighter)
127 127
128 128 #---------------------------------------------------------------------------
129 129 # 'object' interface
130 130 #---------------------------------------------------------------------------
131 131
132 132 def __init__(self, *args, **kw):
133 133 super(FrontendWidget, self).__init__(*args, **kw)
134 134 # FIXME: remove this when PySide min version is updated past 1.0.7
135 135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
136 136 if qt.QT_API == qt.QT_API_PYSIDE:
137 137 import PySide
138 138 if PySide.__version_info__ < (1,0,7):
139 139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
140 140 self.enable_calltips = False
141 141
142 142 # FrontendWidget protected variables.
143 143 self._bracket_matcher = BracketMatcher(self._control)
144 144 self._call_tip_widget = CallTipWidget(self._control)
145 145 self._completion_lexer = CompletionLexer(PythonLexer())
146 146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 147 self._hidden = False
148 148 self._highlighter = FrontendHighlighter(self)
149 149 self._input_splitter = self._input_splitter_class()
150 150 self._kernel_manager = None
151 self._kernel_client = None
151 152 self._request_info = {}
152 153 self._request_info['execute'] = {};
153 154 self._callback_dict = {}
154 155
155 156 # Configure the ConsoleWidget.
156 157 self.tab_width = 4
157 158 self._set_continuation_prompt('... ')
158 159
159 160 # Configure the CallTipWidget.
160 161 self._call_tip_widget.setFont(self.font)
161 162 self.font_changed.connect(self._call_tip_widget.setFont)
162 163
163 164 # Configure actions.
164 165 action = self._copy_raw_action
165 166 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
166 167 action.setEnabled(False)
167 168 action.setShortcut(QtGui.QKeySequence(key))
168 169 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
169 170 action.triggered.connect(self.copy_raw)
170 171 self.copy_available.connect(action.setEnabled)
171 172 self.addAction(action)
172 173
173 174 # Connect signal handlers.
174 175 document = self._control.document()
175 176 document.contentsChange.connect(self._document_contents_change)
176 177
177 178 # Set flag for whether we are connected via localhost.
178 179 self._local_kernel = kw.get('local_kernel',
179 180 FrontendWidget._local_kernel)
180 181
181 182 #---------------------------------------------------------------------------
182 183 # 'ConsoleWidget' public interface
183 184 #---------------------------------------------------------------------------
184 185
185 186 def copy(self):
186 187 """ Copy the currently selected text to the clipboard, removing prompts.
187 188 """
188 189 if self._page_control is not None and self._page_control.hasFocus():
189 190 self._page_control.copy()
190 191 elif self._control.hasFocus():
191 192 text = self._control.textCursor().selection().toPlainText()
192 193 if text:
193 194 text = self._prompt_transformer.transform_cell(text)
194 195 QtGui.QApplication.clipboard().setText(text)
195 196 else:
196 197 self.log.debug("frontend widget : unknown copy target")
197 198
198 199 #---------------------------------------------------------------------------
199 200 # 'ConsoleWidget' abstract interface
200 201 #---------------------------------------------------------------------------
201 202
202 203 def _is_complete(self, source, interactive):
203 204 """ Returns whether 'source' can be completely processed and a new
204 205 prompt created. When triggered by an Enter/Return key press,
205 206 'interactive' is True; otherwise, it is False.
206 207 """
207 208 self._input_splitter.reset()
208 209 complete = self._input_splitter.push(source)
209 210 if interactive:
210 211 complete = not self._input_splitter.push_accepts_more()
211 212 return complete
212 213
213 214 def _execute(self, source, hidden):
214 215 """ Execute 'source'. If 'hidden', do not show any output.
215 216
216 217 See parent class :meth:`execute` docstring for full details.
217 218 """
218 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
219 msg_id = self.kernel_client.execute(source, hidden)
219 220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 221 self._hidden = hidden
221 222 if not hidden:
222 223 self.executing.emit(source)
223 224
224 225 def _prompt_started_hook(self):
225 226 """ Called immediately after a new prompt is displayed.
226 227 """
227 228 if not self._reading:
228 229 self._highlighter.highlighting_on = True
229 230
230 231 def _prompt_finished_hook(self):
231 232 """ Called immediately after a prompt is finished, i.e. when some input
232 233 will be processed and a new prompt displayed.
233 234 """
234 235 # Flush all state from the input splitter so the next round of
235 236 # reading input starts with a clean buffer.
236 237 self._input_splitter.reset()
237 238
238 239 if not self._reading:
239 240 self._highlighter.highlighting_on = False
240 241
241 242 def _tab_pressed(self):
242 243 """ Called when the tab key is pressed. Returns whether to continue
243 244 processing the event.
244 245 """
245 246 # Perform tab completion if:
246 247 # 1) The cursor is in the input buffer.
247 248 # 2) There is a non-whitespace character before the cursor.
248 249 text = self._get_input_buffer_cursor_line()
249 250 if text is None:
250 251 return False
251 252 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
252 253 if complete:
253 254 self._complete()
254 255 return not complete
255 256
256 257 #---------------------------------------------------------------------------
257 258 # 'ConsoleWidget' protected interface
258 259 #---------------------------------------------------------------------------
259 260
260 261 def _context_menu_make(self, pos):
261 262 """ Reimplemented to add an action for raw copy.
262 263 """
263 264 menu = super(FrontendWidget, self)._context_menu_make(pos)
264 265 for before_action in menu.actions():
265 266 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
266 267 QtGui.QKeySequence.ExactMatch:
267 268 menu.insertAction(before_action, self._copy_raw_action)
268 269 break
269 270 return menu
270 271
271 272 def request_interrupt_kernel(self):
272 273 if self._executing:
273 274 self.interrupt_kernel()
274 275
275 276 def request_restart_kernel(self):
276 277 message = 'Are you sure you want to restart the kernel?'
277 278 self.restart_kernel(message, now=False)
278 279
279 280 def _event_filter_console_keypress(self, event):
280 281 """ Reimplemented for execution interruption and smart backspace.
281 282 """
282 283 key = event.key()
283 284 if self._control_key_down(event.modifiers(), include_command=False):
284 285
285 286 if key == QtCore.Qt.Key_C and self._executing:
286 287 self.request_interrupt_kernel()
287 288 return True
288 289
289 290 elif key == QtCore.Qt.Key_Period:
290 291 self.request_restart_kernel()
291 292 return True
292 293
293 294 elif not event.modifiers() & QtCore.Qt.AltModifier:
294 295
295 296 # Smart backspace: remove four characters in one backspace if:
296 297 # 1) everything left of the cursor is whitespace
297 298 # 2) the four characters immediately left of the cursor are spaces
298 299 if key == QtCore.Qt.Key_Backspace:
299 300 col = self._get_input_buffer_cursor_column()
300 301 cursor = self._control.textCursor()
301 302 if col > 3 and not cursor.hasSelection():
302 303 text = self._get_input_buffer_cursor_line()[:col]
303 304 if text.endswith(' ') and not text.strip():
304 305 cursor.movePosition(QtGui.QTextCursor.Left,
305 306 QtGui.QTextCursor.KeepAnchor, 4)
306 307 cursor.removeSelectedText()
307 308 return True
308 309
309 310 return super(FrontendWidget, self)._event_filter_console_keypress(event)
310 311
311 312 def _insert_continuation_prompt(self, cursor):
312 313 """ Reimplemented for auto-indentation.
313 314 """
314 315 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
315 316 cursor.insertText(' ' * self._input_splitter.indent_spaces)
316 317
317 318 #---------------------------------------------------------------------------
318 319 # 'BaseFrontendMixin' abstract interface
319 320 #---------------------------------------------------------------------------
320 321
321 322 def _handle_complete_reply(self, rep):
322 323 """ Handle replies for tab completion.
323 324 """
324 325 self.log.debug("complete: %s", rep.get('content', ''))
325 326 cursor = self._get_cursor()
326 327 info = self._request_info.get('complete')
327 328 if info and info.id == rep['parent_header']['msg_id'] and \
328 329 info.pos == cursor.position():
329 330 text = '.'.join(self._get_context())
330 331 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
331 332 self._complete_with_items(cursor, rep['content']['matches'])
332 333
333 334 def _silent_exec_callback(self, expr, callback):
334 335 """Silently execute `expr` in the kernel and call `callback` with reply
335 336
336 337 the `expr` is evaluated silently in the kernel (without) output in
337 338 the frontend. Call `callback` with the
338 339 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
339 340
340 341 Parameters
341 342 ----------
342 343 expr : string
343 344 valid string to be executed by the kernel.
344 345 callback : function
345 346 function accepting one argument, as a string. The string will be
346 347 the `repr` of the result of evaluating `expr`
347 348
348 349 The `callback` is called with the `repr()` of the result of `expr` as
349 350 first argument. To get the object, do `eval()` on the passed value.
350 351
351 352 See Also
352 353 --------
353 354 _handle_exec_callback : private method, deal with calling callback with reply
354 355
355 356 """
356 357
357 358 # generate uuid, which would be used as an indication of whether or
358 359 # not the unique request originated from here (can use msg id ?)
359 360 local_uuid = str(uuid.uuid1())
360 msg_id = self.kernel_manager.shell_channel.execute('',
361 msg_id = self.kernel_client.execute('',
361 362 silent=True, user_expressions={ local_uuid:expr })
362 363 self._callback_dict[local_uuid] = callback
363 364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
364 365
365 366 def _handle_exec_callback(self, msg):
366 367 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
367 368
368 369 Parameters
369 370 ----------
370 371 msg : raw message send by the kernel containing an `user_expressions`
371 372 and having a 'silent_exec_callback' kind.
372 373
373 374 Notes
374 375 -----
375 376 This function will look for a `callback` associated with the
376 377 corresponding message id. Association has been made by
377 378 `_silent_exec_callback`. `callback` is then called with the `repr()`
378 379 of the value of corresponding `user_expressions` as argument.
379 380 `callback` is then removed from the known list so that any message
380 381 coming again with the same id won't trigger it.
381 382
382 383 """
383 384
384 385 user_exp = msg['content'].get('user_expressions')
385 386 if not user_exp:
386 387 return
387 388 for expression in user_exp:
388 389 if expression in self._callback_dict:
389 390 self._callback_dict.pop(expression)(user_exp[expression])
390 391
391 392 def _handle_execute_reply(self, msg):
392 393 """ Handles replies for code execution.
393 394 """
394 395 self.log.debug("execute: %s", msg.get('content', ''))
395 396 msg_id = msg['parent_header']['msg_id']
396 397 info = self._request_info['execute'].get(msg_id)
397 398 # unset reading flag, because if execute finished, raw_input can't
398 399 # still be pending.
399 400 self._reading = False
400 401 if info and info.kind == 'user' and not self._hidden:
401 402 # Make sure that all output from the SUB channel has been processed
402 403 # before writing a new prompt.
403 self.kernel_manager.iopub_channel.flush()
404 self.kernel_client.iopub_channel.flush()
404 405
405 406 # Reset the ANSI style information to prevent bad text in stdout
406 407 # from messing up our colors. We're not a true terminal so we're
407 408 # allowed to do this.
408 409 if self.ansi_codes:
409 410 self._ansi_processor.reset_sgr()
410 411
411 412 content = msg['content']
412 413 status = content['status']
413 414 if status == 'ok':
414 415 self._process_execute_ok(msg)
415 416 elif status == 'error':
416 417 self._process_execute_error(msg)
417 418 elif status == 'aborted':
418 419 self._process_execute_abort(msg)
419 420
420 421 self._show_interpreter_prompt_for_reply(msg)
421 422 self.executed.emit(msg)
422 423 self._request_info['execute'].pop(msg_id)
423 424 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
424 425 self._handle_exec_callback(msg)
425 426 self._request_info['execute'].pop(msg_id)
426 427 else:
427 428 super(FrontendWidget, self)._handle_execute_reply(msg)
428 429
429 430 def _handle_input_request(self, msg):
430 431 """ Handle requests for raw_input.
431 432 """
432 433 self.log.debug("input: %s", msg.get('content', ''))
433 434 if self._hidden:
434 435 raise RuntimeError('Request for raw input during hidden execution.')
435 436
436 437 # Make sure that all output from the SUB channel has been processed
437 438 # before entering readline mode.
438 self.kernel_manager.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
439 440
440 441 def callback(line):
441 self.kernel_manager.stdin_channel.input(line)
442 self.kernel_client.stdin_channel.input(line)
442 443 if self._reading:
443 444 self.log.debug("Got second input request, assuming first was interrupted.")
444 445 self._reading = False
445 446 self._readline(msg['content']['prompt'], callback=callback)
446 447
448 def _kernel_restarted_message(self, died=True):
449 msg = "Kernel died, restarting" if died else "Kernel restarting"
450 self._append_html("<br>%s<hr><br>" % msg,
451 before_prompt=False
452 )
453
447 454 def _handle_kernel_died(self, since_last_heartbeat):
448 """ Handle the kernel's death by asking if the user wants to restart.
455 """Handle the kernel's death (if we do not own the kernel).
449 456 """
450 self.log.debug("kernel died: %s", since_last_heartbeat)
457 self.log.warn("kernel died: %s", since_last_heartbeat)
451 458 if self.custom_restart:
452 459 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 460 else:
454 message = 'The kernel heartbeat has been inactive for %.2f ' \
455 'seconds. Do you want to restart the kernel? You may ' \
456 'first want to check the network connection.' % \
457 since_last_heartbeat
458 self.restart_kernel(message, now=True)
461 self._kernel_restarted_message(died=True)
462 self.reset()
463
464 def _handle_kernel_restarted(self, died=True):
465 """Notice that the autorestarter restarted the kernel.
466
467 There's nothing to do but show a message.
468 """
469 self.log.warn("kernel restarted")
470 self._kernel_restarted_message(died=died)
471 self.reset()
459 472
460 473 def _handle_object_info_reply(self, rep):
461 474 """ Handle replies for call tips.
462 475 """
463 476 self.log.debug("oinfo: %s", rep.get('content', ''))
464 477 cursor = self._get_cursor()
465 478 info = self._request_info.get('call_tip')
466 479 if info and info.id == rep['parent_header']['msg_id'] and \
467 480 info.pos == cursor.position():
468 481 # Get the information for a call tip. For now we format the call
469 482 # line as string, later we can pass False to format_call and
470 483 # syntax-highlight it ourselves for nicer formatting in the
471 484 # calltip.
472 485 content = rep['content']
473 486 # if this is from pykernel, 'docstring' will be the only key
474 487 if content.get('ismagic', False):
475 488 # Don't generate a call-tip for magics. Ideally, we should
476 489 # generate a tooltip, but not on ( like we do for actual
477 490 # callables.
478 491 call_info, doc = None, None
479 492 else:
480 493 call_info, doc = call_tip(content, format_call=True)
481 494 if call_info or doc:
482 495 self._call_tip_widget.show_call_info(call_info, doc)
483 496
484 497 def _handle_pyout(self, msg):
485 498 """ Handle display hook output.
486 499 """
487 500 self.log.debug("pyout: %s", msg.get('content', ''))
488 501 if not self._hidden and self._is_from_this_session(msg):
489 502 text = msg['content']['data']
490 503 self._append_plain_text(text + '\n', before_prompt=True)
491 504
492 505 def _handle_stream(self, msg):
493 506 """ Handle stdout, stderr, and stdin.
494 507 """
495 508 self.log.debug("stream: %s", msg.get('content', ''))
496 509 if not self._hidden and self._is_from_this_session(msg):
497 510 # Most consoles treat tabs as being 8 space characters. Convert tabs
498 511 # to spaces so that output looks as expected regardless of this
499 512 # widget's tab width.
500 513 text = msg['content']['data'].expandtabs(8)
501 514
502 515 self._append_plain_text(text, before_prompt=True)
503 516 self._control.moveCursor(QtGui.QTextCursor.End)
504 517
505 518 def _handle_shutdown_reply(self, msg):
506 519 """ Handle shutdown signal, only if from other console.
507 520 """
508 self.log.debug("shutdown: %s", msg.get('content', ''))
521 self.log.warn("shutdown: %s", msg.get('content', ''))
522 restart = msg.get('content', {}).get('restart', False)
509 523 if not self._hidden and not self._is_from_this_session(msg):
524 # got shutdown reply, request came from session other than ours
525 if restart:
526 # someone restarted the kernel, handle it
527 self._handle_kernel_restarted(died=False)
528 else:
529 # kernel was shutdown permanently
530 # this triggers exit_requested if the kernel was local,
531 # and a dialog if the kernel was remote,
532 # so we don't suddenly clear the qtconsole without asking.
510 533 if self._local_kernel:
511 if not msg['content']['restart']:
512 534 self.exit_requested.emit(self)
513 535 else:
514 # we just got notified of a restart!
515 time.sleep(0.25) # wait 1/4 sec to reset
516 # lest the request for a new prompt
517 # goes to the old kernel
518 self.reset()
519 else: # remote kernel, prompt on Kernel shutdown/reset
520 536 title = self.window().windowTitle()
521 if not msg['content']['restart']:
522 537 reply = QtGui.QMessageBox.question(self, title,
523 538 "Kernel has been shutdown permanently. "
524 539 "Close the Console?",
525 540 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 541 if reply == QtGui.QMessageBox.Yes:
527 542 self.exit_requested.emit(self)
528 else:
529 # XXX: remove message box in favor of using the
530 # clear_on_kernel_restart setting?
531 reply = QtGui.QMessageBox.question(self, title,
532 "Kernel has been reset. Clear the Console?",
533 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
534 if reply == QtGui.QMessageBox.Yes:
535 time.sleep(0.25) # wait 1/4 sec to reset
536 # lest the request for a new prompt
537 # goes to the old kernel
538 self.reset()
543
544 def _handle_status(self, msg):
545 """Handle status message"""
546 # This is where a busy/idle indicator would be triggered,
547 # when we make one.
548 state = msg['content'].get('execution_state', '')
549 if state == 'starting':
550 # kernel started while we were running
551 if self._executing:
552 self._handle_kernel_restarted(died=True)
553 elif state == 'idle':
554 pass
555 elif state == 'busy':
556 pass
539 557
540 558 def _started_channels(self):
541 559 """ Called when the KernelManager channels have started listening or
542 560 when the frontend is assigned an already listening KernelManager.
543 561 """
544 562 self.reset(clear=True)
545 563
546 564 #---------------------------------------------------------------------------
547 565 # 'FrontendWidget' public interface
548 566 #---------------------------------------------------------------------------
549 567
550 568 def copy_raw(self):
551 569 """ Copy the currently selected text to the clipboard without attempting
552 570 to remove prompts or otherwise alter the text.
553 571 """
554 572 self._control.copy()
555 573
556 574 def execute_file(self, path, hidden=False):
557 575 """ Attempts to execute file with 'path'. If 'hidden', no output is
558 576 shown.
559 577 """
560 578 self.execute('execfile(%r)' % path, hidden=hidden)
561 579
562 580 def interrupt_kernel(self):
563 581 """ Attempts to interrupt the running kernel.
564 582
565 583 Also unsets _reading flag, to avoid runtime errors
566 584 if raw_input is called again.
567 585 """
568 586 if self.custom_interrupt:
569 587 self._reading = False
570 588 self.custom_interrupt_requested.emit()
571 elif self.kernel_manager.has_kernel:
589 elif self.kernel_manager:
572 590 self._reading = False
573 591 self.kernel_manager.interrupt_kernel()
574 592 else:
575 self._append_plain_text('Kernel process is either remote or '
576 'unspecified. Cannot interrupt.\n')
593 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
577 594
578 595 def reset(self, clear=False):
579 """ Resets the widget to its initial state if ``clear`` parameter or
580 ``clear_on_kernel_restart`` configuration setting is True, otherwise
596 """ Resets the widget to its initial state if ``clear`` parameter
597 is True, otherwise
581 598 prints a visual indication of the fact that the kernel restarted, but
582 599 does not clear the traces from previous usage of the kernel before it
583 600 was restarted. With ``clear=True``, it is similar to ``%clear``, but
584 601 also re-writes the banner and aborts execution if necessary.
585 602 """
586 603 if self._executing:
587 604 self._executing = False
588 605 self._request_info['execute'] = {}
589 606 self._reading = False
590 607 self._highlighter.highlighting_on = False
591 608
592 if self.clear_on_kernel_restart or clear:
609 if clear:
593 610 self._control.clear()
594 611 self._append_plain_text(self.banner)
595 else:
596 self._append_plain_text("# restarting kernel...")
597 self._append_html("<hr><br>")
598 # XXX: Reprinting the full banner may be too much, but once #1680 is
599 # addressed, that will mitigate it.
600 #self._append_plain_text(self.banner)
601 612 # update output marker for stdout/stderr, so that startup
602 613 # messages appear after banner:
603 614 self._append_before_prompt_pos = self._get_cursor().position()
604 615 self._show_interpreter_prompt()
605 616
606 617 def restart_kernel(self, message, now=False):
607 618 """ Attempts to restart the running kernel.
608 619 """
609 620 # FIXME: now should be configurable via a checkbox in the dialog. Right
610 621 # now at least the heartbeat path sets it to True and the manual restart
611 622 # to False. But those should just be the pre-selected states of a
612 623 # checkbox that the user could override if so desired. But I don't know
613 624 # enough Qt to go implementing the checkbox now.
614 625
615 626 if self.custom_restart:
616 627 self.custom_restart_requested.emit()
628 return
617 629
618 elif self.kernel_manager.has_kernel:
630 if self.kernel_manager:
619 631 # Pause the heart beat channel to prevent further warnings.
620 self.kernel_manager.hb_channel.pause()
632 self.kernel_client.hb_channel.pause()
621 633
622 634 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 635 # they decline. (If they accept, the heartbeat will be un-paused
624 636 # automatically when the kernel is restarted.)
625 637 if self.confirm_restart:
626 638 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
627 639 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
628 640 message, buttons)
629 641 do_restart = result == QtGui.QMessageBox.Yes
630 642 else:
631 643 # confirm_restart is False, so we don't need to ask user
632 644 # anything, just do the restart
633 645 do_restart = True
634 646 if do_restart:
635 647 try:
636 648 self.kernel_manager.restart_kernel(now=now)
637 except RuntimeError:
638 self._append_plain_text('Kernel started externally. '
639 'Cannot restart.\n',
649 except RuntimeError as e:
650 self._append_plain_text(
651 'Error restarting kernel: %s\n' % e,
640 652 before_prompt=True
641 653 )
642 654 else:
643 self.reset()
655 self._append_html("<br>Restarting kernel...\n<hr><br>",
656 before_prompt=True,
657 )
644 658 else:
645 self.kernel_manager.hb_channel.unpause()
659 self.kernel_client.hb_channel.unpause()
646 660
647 661 else:
648 self._append_plain_text('Kernel process is either remote or '
649 'unspecified. Cannot restart.\n',
662 self._append_plain_text(
663 'Cannot restart a Kernel I did not start\n',
650 664 before_prompt=True
651 665 )
652 666
653 667 #---------------------------------------------------------------------------
654 668 # 'FrontendWidget' protected interface
655 669 #---------------------------------------------------------------------------
656 670
657 671 def _call_tip(self):
658 672 """ Shows a call tip, if appropriate, at the current cursor location.
659 673 """
660 674 # Decide if it makes sense to show a call tip
661 675 if not self.enable_calltips:
662 676 return False
663 677 cursor = self._get_cursor()
664 678 cursor.movePosition(QtGui.QTextCursor.Left)
665 679 if cursor.document().characterAt(cursor.position()) != '(':
666 680 return False
667 681 context = self._get_context(cursor)
668 682 if not context:
669 683 return False
670 684
671 685 # Send the metadata request to the kernel
672 686 name = '.'.join(context)
673 msg_id = self.kernel_manager.shell_channel.object_info(name)
687 msg_id = self.kernel_client.object_info(name)
674 688 pos = self._get_cursor().position()
675 689 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 690 return True
677 691
678 692 def _complete(self):
679 693 """ Performs completion at the current cursor location.
680 694 """
681 695 context = self._get_context()
682 696 if context:
683 697 # Send the completion request to the kernel
684 msg_id = self.kernel_manager.shell_channel.complete(
698 msg_id = self.kernel_client.complete(
685 699 '.'.join(context), # text
686 700 self._get_input_buffer_cursor_line(), # line
687 701 self._get_input_buffer_cursor_column(), # cursor_pos
688 702 self.input_buffer) # block
689 703 pos = self._get_cursor().position()
690 704 info = self._CompletionRequest(msg_id, pos)
691 705 self._request_info['complete'] = info
692 706
693 707 def _get_context(self, cursor=None):
694 708 """ Gets the context for the specified cursor (or the current cursor
695 709 if none is specified).
696 710 """
697 711 if cursor is None:
698 712 cursor = self._get_cursor()
699 713 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
700 714 QtGui.QTextCursor.KeepAnchor)
701 715 text = cursor.selection().toPlainText()
702 716 return self._completion_lexer.get_context(text)
703 717
704 718 def _process_execute_abort(self, msg):
705 719 """ Process a reply for an aborted execution request.
706 720 """
707 721 self._append_plain_text("ERROR: execution aborted\n")
708 722
709 723 def _process_execute_error(self, msg):
710 724 """ Process a reply for an execution request that resulted in an error.
711 725 """
712 726 content = msg['content']
713 727 # If a SystemExit is passed along, this means exit() was called - also
714 728 # all the ipython %exit magic syntax of '-k' to be used to keep
715 729 # the kernel running
716 730 if content['ename']=='SystemExit':
717 731 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
718 732 self._keep_kernel_on_exit = keepkernel
719 733 self.exit_requested.emit(self)
720 734 else:
721 735 traceback = ''.join(content['traceback'])
722 736 self._append_plain_text(traceback)
723 737
724 738 def _process_execute_ok(self, msg):
725 739 """ Process a reply for a successful execution request.
726 740 """
727 741 payload = msg['content']['payload']
728 742 for item in payload:
729 743 if not self._process_execute_payload(item):
730 744 warning = 'Warning: received unknown payload of type %s'
731 745 print(warning % repr(item['source']))
732 746
733 747 def _process_execute_payload(self, item):
734 748 """ Process a single payload item from the list of payload items in an
735 749 execution reply. Returns whether the payload was handled.
736 750 """
737 751 # The basic FrontendWidget doesn't handle payloads, as they are a
738 752 # mechanism for going beyond the standard Python interpreter model.
739 753 return False
740 754
741 755 def _show_interpreter_prompt(self):
742 756 """ Shows a prompt for the interpreter.
743 757 """
744 758 self._show_prompt('>>> ')
745 759
746 760 def _show_interpreter_prompt_for_reply(self, msg):
747 761 """ Shows a prompt for the interpreter given an 'execute_reply' message.
748 762 """
749 763 self._show_interpreter_prompt()
750 764
751 765 #------ Signal handlers ----------------------------------------------------
752 766
753 767 def _document_contents_change(self, position, removed, added):
754 768 """ Called whenever the document's content changes. Display a call tip
755 769 if appropriate.
756 770 """
757 771 # Calculate where the cursor should be *after* the change:
758 772 position += added
759 773
760 774 document = self._control.document()
761 775 if position == self._get_cursor().position():
762 776 self._call_tip()
763 777
764 778 #------ Trait default initializers -----------------------------------------
765 779
766 780 def _banner_default(self):
767 781 """ Returns the standard Python banner.
768 782 """
769 783 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
770 784 '"license" for more information.'
771 785 return banner % (sys.version, sys.platform)
@@ -1,302 +1,302
1 1 # System library imports
2 2 from IPython.external.qt import QtGui
3 3
4 4 # Local imports
5 5 from IPython.utils.traitlets import Bool
6 6 from console_widget import ConsoleWidget
7 7
8 8
9 9 class HistoryConsoleWidget(ConsoleWidget):
10 10 """ A ConsoleWidget that keeps a history of the commands that have been
11 11 executed and provides a readline-esque interface to this history.
12 12 """
13 13
14 14 #------ Configuration ------------------------------------------------------
15 15
16 16 # If enabled, the input buffer will become "locked" to history movement when
17 17 # an edit is made to a multi-line input buffer. To override the lock, use
18 18 # Shift in conjunction with the standard history cycling keys.
19 19 history_lock = Bool(False, config=True)
20 20
21 21 #---------------------------------------------------------------------------
22 22 # 'object' interface
23 23 #---------------------------------------------------------------------------
24 24
25 25 def __init__(self, *args, **kw):
26 26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27 27
28 28 # HistoryConsoleWidget protected variables.
29 29 self._history = []
30 30 self._history_edits = {}
31 31 self._history_index = 0
32 32 self._history_prefix = ''
33 33
34 34 #---------------------------------------------------------------------------
35 35 # 'ConsoleWidget' public interface
36 36 #---------------------------------------------------------------------------
37 37
38 38 def execute(self, source=None, hidden=False, interactive=False):
39 39 """ Reimplemented to the store history.
40 40 """
41 41 if not hidden:
42 42 history = self.input_buffer if source is None else source
43 43
44 44 executed = super(HistoryConsoleWidget, self).execute(
45 45 source, hidden, interactive)
46 46
47 47 if executed and not hidden:
48 48 # Save the command unless it was an empty string or was identical
49 49 # to the previous command.
50 50 history = history.rstrip()
51 51 if history and (not self._history or self._history[-1] != history):
52 52 self._history.append(history)
53 53
54 54 # Emulate readline: reset all history edits.
55 55 self._history_edits = {}
56 56
57 57 # Move the history index to the most recent item.
58 58 self._history_index = len(self._history)
59 59
60 60 return executed
61 61
62 62 #---------------------------------------------------------------------------
63 63 # 'ConsoleWidget' abstract interface
64 64 #---------------------------------------------------------------------------
65 65
66 66 def _up_pressed(self, shift_modifier):
67 67 """ Called when the up key is pressed. Returns whether to continue
68 68 processing the event.
69 69 """
70 70 prompt_cursor = self._get_prompt_cursor()
71 71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 72 # Bail out if we're locked.
73 73 if self._history_locked() and not shift_modifier:
74 74 return False
75 75
76 76 # Set a search prefix based on the cursor position.
77 77 col = self._get_input_buffer_cursor_column()
78 78 input_buffer = self.input_buffer
79 79 # use the *shortest* of the cursor column and the history prefix
80 80 # to determine if the prefix has changed
81 81 n = min(col, len(self._history_prefix))
82 82
83 83 # prefix changed, restart search from the beginning
84 84 if (self._history_prefix[:n] != input_buffer[:n]):
85 85 self._history_index = len(self._history)
86 86
87 87 # the only time we shouldn't set the history prefix
88 88 # to the line up to the cursor is if we are already
89 89 # in a simple scroll (no prefix),
90 90 # and the cursor is at the end of the first line
91 91
92 92 # check if we are at the end of the first line
93 93 c = self._get_cursor()
94 94 current_pos = c.position()
95 95 c.movePosition(QtGui.QTextCursor.EndOfLine)
96 96 at_eol = (c.position() == current_pos)
97 97
98 98 if self._history_index == len(self._history) or \
99 99 not (self._history_prefix == '' and at_eol) or \
100 100 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
101 101 self._history_prefix = input_buffer[:col]
102 102
103 103 # Perform the search.
104 104 self.history_previous(self._history_prefix,
105 105 as_prefix=not shift_modifier)
106 106
107 107 # Go to the first line of the prompt for seemless history scrolling.
108 108 # Emulate readline: keep the cursor position fixed for a prefix
109 109 # search.
110 110 cursor = self._get_prompt_cursor()
111 111 if self._history_prefix:
112 112 cursor.movePosition(QtGui.QTextCursor.Right,
113 113 n=len(self._history_prefix))
114 114 else:
115 115 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
116 116 self._set_cursor(cursor)
117 117
118 118 return False
119 119
120 120 return True
121 121
122 122 def _down_pressed(self, shift_modifier):
123 123 """ Called when the down key is pressed. Returns whether to continue
124 124 processing the event.
125 125 """
126 126 end_cursor = self._get_end_cursor()
127 127 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
128 128 # Bail out if we're locked.
129 129 if self._history_locked() and not shift_modifier:
130 130 return False
131 131
132 132 # Perform the search.
133 133 replaced = self.history_next(self._history_prefix,
134 134 as_prefix=not shift_modifier)
135 135
136 136 # Emulate readline: keep the cursor position fixed for a prefix
137 137 # search. (We don't need to move the cursor to the end of the buffer
138 138 # in the other case because this happens automatically when the
139 139 # input buffer is set.)
140 140 if self._history_prefix and replaced:
141 141 cursor = self._get_prompt_cursor()
142 142 cursor.movePosition(QtGui.QTextCursor.Right,
143 143 n=len(self._history_prefix))
144 144 self._set_cursor(cursor)
145 145
146 146 return False
147 147
148 148 return True
149 149
150 150 #---------------------------------------------------------------------------
151 151 # 'HistoryConsoleWidget' public interface
152 152 #---------------------------------------------------------------------------
153 153
154 154 def history_previous(self, substring='', as_prefix=True):
155 155 """ If possible, set the input buffer to a previous history item.
156 156
157 157 Parameters:
158 158 -----------
159 159 substring : str, optional
160 160 If specified, search for an item with this substring.
161 161 as_prefix : bool, optional
162 162 If True, the substring must match at the beginning (default).
163 163
164 164 Returns:
165 165 --------
166 166 Whether the input buffer was changed.
167 167 """
168 168 index = self._history_index
169 169 replace = False
170 170 while index > 0:
171 171 index -= 1
172 172 history = self._get_edited_history(index)
173 173 if (as_prefix and history.startswith(substring)) \
174 174 or (not as_prefix and substring in history):
175 175 replace = True
176 176 break
177 177
178 178 if replace:
179 179 self._store_edits()
180 180 self._history_index = index
181 181 self.input_buffer = history
182 182
183 183 return replace
184 184
185 185 def history_next(self, substring='', as_prefix=True):
186 186 """ If possible, set the input buffer to a subsequent history item.
187 187
188 188 Parameters:
189 189 -----------
190 190 substring : str, optional
191 191 If specified, search for an item with this substring.
192 192 as_prefix : bool, optional
193 193 If True, the substring must match at the beginning (default).
194 194
195 195 Returns:
196 196 --------
197 197 Whether the input buffer was changed.
198 198 """
199 199 index = self._history_index
200 200 replace = False
201 201 while index < len(self._history):
202 202 index += 1
203 203 history = self._get_edited_history(index)
204 204 if (as_prefix and history.startswith(substring)) \
205 205 or (not as_prefix and substring in history):
206 206 replace = True
207 207 break
208 208
209 209 if replace:
210 210 self._store_edits()
211 211 self._history_index = index
212 212 self.input_buffer = history
213 213
214 214 return replace
215 215
216 216 def history_tail(self, n=10):
217 217 """ Get the local history list.
218 218
219 219 Parameters:
220 220 -----------
221 221 n : int
222 222 The (maximum) number of history items to get.
223 223 """
224 224 return self._history[-n:]
225 225
226 226 def _request_update_session_history_length(self):
227 msg_id = self.kernel_manager.shell_channel.execute('',
227 msg_id = self.kernel_client.shell_channel.execute('',
228 228 silent=True,
229 229 user_expressions={
230 230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
231 231 }
232 232 )
233 233 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
234 234
235 235 def _handle_execute_reply(self, msg):
236 236 """ Handles replies for code execution, here only session history length
237 237 """
238 238 msg_id = msg['parent_header']['msg_id']
239 239 info = self._request_info['execute'].pop(msg_id,None)
240 240 if info and info.kind == 'save_magic' and not self._hidden:
241 241 content = msg['content']
242 242 status = content['status']
243 243 if status == 'ok':
244 244 self._max_session_history=(int(content['user_expressions']['hlen']))
245 245
246 246 def save_magic(self):
247 247 # update the session history length
248 248 self._request_update_session_history_length()
249 249
250 250 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
251 251 "Enter A filename",
252 252 filter='Python File (*.py);; All files (*.*)'
253 253 )
254 254
255 255 # let's the user search/type for a file name, while the history length
256 256 # is fetched
257 257
258 258 if file_name:
259 259 hist_range, ok = QtGui.QInputDialog.getText(self,
260 260 'Please enter an interval of command to save',
261 261 'Saving commands:',
262 262 text=str('1-'+str(self._max_session_history))
263 263 )
264 264 if ok:
265 265 self.execute("%save"+" "+file_name+" "+str(hist_range))
266 266
267 267 #---------------------------------------------------------------------------
268 268 # 'HistoryConsoleWidget' protected interface
269 269 #---------------------------------------------------------------------------
270 270
271 271 def _history_locked(self):
272 272 """ Returns whether history movement is locked.
273 273 """
274 274 return (self.history_lock and
275 275 (self._get_edited_history(self._history_index) !=
276 276 self.input_buffer) and
277 277 (self._get_prompt_cursor().blockNumber() !=
278 278 self._get_end_cursor().blockNumber()))
279 279
280 280 def _get_edited_history(self, index):
281 281 """ Retrieves a history item, possibly with temporary edits.
282 282 """
283 283 if index in self._history_edits:
284 284 return self._history_edits[index]
285 285 elif index == len(self._history):
286 286 return unicode()
287 287 return self._history[index]
288 288
289 289 def _set_history(self, history):
290 290 """ Replace the current history with a sequence of history items.
291 291 """
292 292 self._history = list(history)
293 293 self._history_edits = {}
294 294 self._history_index = len(self._history)
295 295
296 296 def _store_edits(self):
297 297 """ If there are edits to the current input buffer, store them.
298 298 """
299 299 current = self.input_buffer
300 300 if self._history_index == len(self._history) or \
301 301 self._history[self._history_index] != current:
302 302 self._history_edits[self._history_index] = current
@@ -1,584 +1,584
1 1 """ A FrontendWidget that emulates the interface of the console IPython and
2 2 supports the additional functionality provided by the IPython kernel.
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Imports
7 7 #-----------------------------------------------------------------------------
8 8
9 9 # Standard library imports
10 10 from collections import namedtuple
11 11 import os.path
12 12 import re
13 13 from subprocess import Popen
14 14 import sys
15 15 import time
16 16 from textwrap import dedent
17 17
18 18 # System library imports
19 19 from IPython.external.qt import QtCore, QtGui
20 20
21 21 # Local imports
22 22 from IPython.core.inputsplitter import IPythonInputSplitter
23 23 from IPython.core.inputtransformer import ipy_prompt
24 24 from IPython.utils.traitlets import Bool, Unicode
25 25 from frontend_widget import FrontendWidget
26 26 import styles
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Constants
30 30 #-----------------------------------------------------------------------------
31 31
32 32 # Default strings to build and display input and output prompts (and separators
33 33 # in between)
34 34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 36 default_input_sep = '\n'
37 37 default_output_sep = ''
38 38 default_output_sep2 = ''
39 39
40 40 # Base path for most payload sources.
41 41 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
42 42
43 43 if sys.platform.startswith('win'):
44 44 default_editor = 'notepad'
45 45 else:
46 46 default_editor = ''
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # IPythonWidget class
50 50 #-----------------------------------------------------------------------------
51 51
52 52 class IPythonWidget(FrontendWidget):
53 53 """ A FrontendWidget for an IPython kernel.
54 54 """
55 55
56 56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 58 # settings.
59 59 custom_edit = Bool(False)
60 60 custom_edit_requested = QtCore.Signal(object, object)
61 61
62 62 editor = Unicode(default_editor, config=True,
63 63 help="""
64 64 A command for invoking a system text editor. If the string contains a
65 65 {filename} format specifier, it will be used. Otherwise, the filename
66 66 will be appended to the end the command.
67 67 """)
68 68
69 69 editor_line = Unicode(config=True,
70 70 help="""
71 71 The editor command to use when a specific line number is requested. The
72 72 string should contain two format specifiers: {line} and {filename}. If
73 73 this parameter is not specified, the line number option to the %edit
74 74 magic will be ignored.
75 75 """)
76 76
77 77 style_sheet = Unicode(config=True,
78 78 help="""
79 79 A CSS stylesheet. The stylesheet can contain classes for:
80 80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 83 """)
84 84
85 85 syntax_style = Unicode(config=True,
86 86 help="""
87 87 If not empty, use this Pygments style for syntax highlighting.
88 88 Otherwise, the style sheet is queried for Pygments style
89 89 information.
90 90 """)
91 91
92 92 # Prompts.
93 93 in_prompt = Unicode(default_in_prompt, config=True)
94 94 out_prompt = Unicode(default_out_prompt, config=True)
95 95 input_sep = Unicode(default_input_sep, config=True)
96 96 output_sep = Unicode(default_output_sep, config=True)
97 97 output_sep2 = Unicode(default_output_sep2, config=True)
98 98
99 99 # FrontendWidget protected class variables.
100 100 _input_splitter_class = IPythonInputSplitter
101 101 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
102 102 logical_line_transforms=[],
103 103 python_line_transforms=[],
104 104 )
105 105
106 106 # IPythonWidget protected class variables.
107 107 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
108 108 _payload_source_edit = zmq_shell_source + '.edit_magic'
109 109 _payload_source_exit = zmq_shell_source + '.ask_exit'
110 110 _payload_source_next_input = zmq_shell_source + '.set_next_input'
111 111 _payload_source_page = 'IPython.kernel.zmq.page.page'
112 112 _retrying_history_request = False
113 113
114 114 #---------------------------------------------------------------------------
115 115 # 'object' interface
116 116 #---------------------------------------------------------------------------
117 117
118 118 def __init__(self, *args, **kw):
119 119 super(IPythonWidget, self).__init__(*args, **kw)
120 120
121 121 # IPythonWidget protected variables.
122 122 self._payload_handlers = {
123 123 self._payload_source_edit : self._handle_payload_edit,
124 124 self._payload_source_exit : self._handle_payload_exit,
125 125 self._payload_source_page : self._handle_payload_page,
126 126 self._payload_source_next_input : self._handle_payload_next_input }
127 127 self._previous_prompt_obj = None
128 128 self._keep_kernel_on_exit = None
129 129
130 130 # Initialize widget styling.
131 131 if self.style_sheet:
132 132 self._style_sheet_changed()
133 133 self._syntax_style_changed()
134 134 else:
135 135 self.set_default_style()
136 136
137 137 #---------------------------------------------------------------------------
138 138 # 'BaseFrontendMixin' abstract interface
139 139 #---------------------------------------------------------------------------
140 140
141 141 def _handle_complete_reply(self, rep):
142 142 """ Reimplemented to support IPython's improved completion machinery.
143 143 """
144 144 self.log.debug("complete: %s", rep.get('content', ''))
145 145 cursor = self._get_cursor()
146 146 info = self._request_info.get('complete')
147 147 if info and info.id == rep['parent_header']['msg_id'] and \
148 148 info.pos == cursor.position():
149 149 matches = rep['content']['matches']
150 150 text = rep['content']['matched_text']
151 151 offset = len(text)
152 152
153 153 # Clean up matches with period and path separators if the matched
154 154 # text has not been transformed. This is done by truncating all
155 155 # but the last component and then suitably decreasing the offset
156 156 # between the current cursor position and the start of completion.
157 157 if len(matches) > 1 and matches[0][:offset] == text:
158 158 parts = re.split(r'[./\\]', text)
159 159 sep_count = len(parts) - 1
160 160 if sep_count:
161 161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
162 162 matches = [ match[chop_length:] for match in matches ]
163 163 offset -= chop_length
164 164
165 165 # Move the cursor to the start of the match and complete.
166 166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
167 167 self._complete_with_items(cursor, matches)
168 168
169 169 def _handle_execute_reply(self, msg):
170 170 """ Reimplemented to support prompt requests.
171 171 """
172 172 msg_id = msg['parent_header'].get('msg_id')
173 173 info = self._request_info['execute'].get(msg_id)
174 174 if info and info.kind == 'prompt':
175 175 number = msg['content']['execution_count'] + 1
176 176 self._show_interpreter_prompt(number)
177 177 self._request_info['execute'].pop(msg_id)
178 178 else:
179 179 super(IPythonWidget, self)._handle_execute_reply(msg)
180 180
181 181 def _handle_history_reply(self, msg):
182 182 """ Implemented to handle history tail replies, which are only supported
183 183 by the IPython kernel.
184 184 """
185 185 content = msg['content']
186 186 if 'history' not in content:
187 187 self.log.error("History request failed: %r"%content)
188 188 if content.get('status', '') == 'aborted' and \
189 189 not self._retrying_history_request:
190 190 # a *different* action caused this request to be aborted, so
191 191 # we should try again.
192 192 self.log.error("Retrying aborted history request")
193 193 # prevent multiple retries of aborted requests:
194 194 self._retrying_history_request = True
195 195 # wait out the kernel's queue flush, which is currently timed at 0.1s
196 196 time.sleep(0.25)
197 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
197 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
198 198 else:
199 199 self._retrying_history_request = False
200 200 return
201 201 # reset retry flag
202 202 self._retrying_history_request = False
203 203 history_items = content['history']
204 204 self.log.debug("Received history reply with %i entries", len(history_items))
205 205 items = []
206 206 last_cell = u""
207 207 for _, _, cell in history_items:
208 208 cell = cell.rstrip()
209 209 if cell != last_cell:
210 210 items.append(cell)
211 211 last_cell = cell
212 212 self._set_history(items)
213 213
214 214 def _handle_pyout(self, msg):
215 215 """ Reimplemented for IPython-style "display hook".
216 216 """
217 217 self.log.debug("pyout: %s", msg.get('content', ''))
218 218 if not self._hidden and self._is_from_this_session(msg):
219 219 content = msg['content']
220 220 prompt_number = content.get('execution_count', 0)
221 221 data = content['data']
222 222 if 'text/html' in data:
223 223 self._append_plain_text(self.output_sep, True)
224 224 self._append_html(self._make_out_prompt(prompt_number), True)
225 225 html = data['text/html']
226 226 self._append_plain_text('\n', True)
227 227 self._append_html(html + self.output_sep2, True)
228 228 elif 'text/plain' in data:
229 229 self._append_plain_text(self.output_sep, True)
230 230 self._append_html(self._make_out_prompt(prompt_number), True)
231 231 text = data['text/plain']
232 232 # If the repr is multiline, make sure we start on a new line,
233 233 # so that its lines are aligned.
234 234 if "\n" in text and not self.output_sep.endswith("\n"):
235 235 self._append_plain_text('\n', True)
236 236 self._append_plain_text(text + self.output_sep2, True)
237 237
238 238 def _handle_display_data(self, msg):
239 239 """ The base handler for the ``display_data`` message.
240 240 """
241 241 self.log.debug("display: %s", msg.get('content', ''))
242 242 # For now, we don't display data from other frontends, but we
243 243 # eventually will as this allows all frontends to monitor the display
244 244 # data. But we need to figure out how to handle this in the GUI.
245 245 if not self._hidden and self._is_from_this_session(msg):
246 246 source = msg['content']['source']
247 247 data = msg['content']['data']
248 248 metadata = msg['content']['metadata']
249 249 # In the regular IPythonWidget, we simply print the plain text
250 250 # representation.
251 251 if 'text/html' in data:
252 252 html = data['text/html']
253 253 self._append_html(html, True)
254 254 elif 'text/plain' in data:
255 255 text = data['text/plain']
256 256 self._append_plain_text(text, True)
257 257 # This newline seems to be needed for text and html output.
258 258 self._append_plain_text(u'\n', True)
259 259
260 260 def _started_channels(self):
261 261 """Reimplemented to make a history request and load %guiref."""
262 262 super(IPythonWidget, self)._started_channels()
263 263 self._load_guiref_magic()
264 self.kernel_manager.shell_channel.history(hist_access_type='tail',
264 self.kernel_client.shell_channel.history(hist_access_type='tail',
265 265 n=1000)
266 266
267 267 def _started_kernel(self):
268 268 """Load %guiref when the kernel starts (if channels are also started).
269 269
270 270 Principally triggered by kernel restart.
271 271 """
272 if self.kernel_manager.shell_channel is not None:
272 if self.kernel_client.shell_channel is not None:
273 273 self._load_guiref_magic()
274 274
275 275 def _load_guiref_magic(self):
276 276 """Load %guiref magic."""
277 self.kernel_manager.shell_channel.execute('\n'.join([
277 self.kernel_client.shell_channel.execute('\n'.join([
278 278 "try:",
279 279 " _usage",
280 280 "except:",
281 281 " from IPython.core import usage as _usage",
282 282 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
283 283 " del _usage",
284 284 ]), silent=True)
285 285
286 286 #---------------------------------------------------------------------------
287 287 # 'ConsoleWidget' public interface
288 288 #---------------------------------------------------------------------------
289 289
290 290 #---------------------------------------------------------------------------
291 291 # 'FrontendWidget' public interface
292 292 #---------------------------------------------------------------------------
293 293
294 294 def execute_file(self, path, hidden=False):
295 295 """ Reimplemented to use the 'run' magic.
296 296 """
297 297 # Use forward slashes on Windows to avoid escaping each separator.
298 298 if sys.platform == 'win32':
299 299 path = os.path.normpath(path).replace('\\', '/')
300 300
301 301 # Perhaps we should not be using %run directly, but while we
302 302 # are, it is necessary to quote or escape filenames containing spaces
303 303 # or quotes.
304 304
305 305 # In earlier code here, to minimize escaping, we sometimes quoted the
306 306 # filename with single quotes. But to do this, this code must be
307 307 # platform-aware, because run uses shlex rather than python string
308 308 # parsing, so that:
309 309 # * In Win: single quotes can be used in the filename without quoting,
310 310 # and we cannot use single quotes to quote the filename.
311 311 # * In *nix: we can escape double quotes in a double quoted filename,
312 312 # but can't escape single quotes in a single quoted filename.
313 313
314 314 # So to keep this code non-platform-specific and simple, we now only
315 315 # use double quotes to quote filenames, and escape when needed:
316 316 if ' ' in path or "'" in path or '"' in path:
317 317 path = '"%s"' % path.replace('"', '\\"')
318 318 self.execute('%%run %s' % path, hidden=hidden)
319 319
320 320 #---------------------------------------------------------------------------
321 321 # 'FrontendWidget' protected interface
322 322 #---------------------------------------------------------------------------
323 323
324 324 def _complete(self):
325 325 """ Reimplemented to support IPython's improved completion machinery.
326 326 """
327 327 # We let the kernel split the input line, so we *always* send an empty
328 328 # text field. Readline-based frontends do get a real text field which
329 329 # they can use.
330 330 text = ''
331 331
332 332 # Send the completion request to the kernel
333 msg_id = self.kernel_manager.shell_channel.complete(
333 msg_id = self.kernel_client.shell_channel.complete(
334 334 text, # text
335 335 self._get_input_buffer_cursor_line(), # line
336 336 self._get_input_buffer_cursor_column(), # cursor_pos
337 337 self.input_buffer) # block
338 338 pos = self._get_cursor().position()
339 339 info = self._CompletionRequest(msg_id, pos)
340 340 self._request_info['complete'] = info
341 341
342 342 def _process_execute_error(self, msg):
343 343 """ Reimplemented for IPython-style traceback formatting.
344 344 """
345 345 content = msg['content']
346 346 traceback = '\n'.join(content['traceback']) + '\n'
347 347 if False:
348 348 # FIXME: For now, tracebacks come as plain text, so we can't use
349 349 # the html renderer yet. Once we refactor ultratb to produce
350 350 # properly styled tracebacks, this branch should be the default
351 351 traceback = traceback.replace(' ', '&nbsp;')
352 352 traceback = traceback.replace('\n', '<br/>')
353 353
354 354 ename = content['ename']
355 355 ename_styled = '<span class="error">%s</span>' % ename
356 356 traceback = traceback.replace(ename, ename_styled)
357 357
358 358 self._append_html(traceback)
359 359 else:
360 360 # This is the fallback for now, using plain text with ansi escapes
361 361 self._append_plain_text(traceback)
362 362
363 363 def _process_execute_payload(self, item):
364 364 """ Reimplemented to dispatch payloads to handler methods.
365 365 """
366 366 handler = self._payload_handlers.get(item['source'])
367 367 if handler is None:
368 368 # We have no handler for this type of payload, simply ignore it
369 369 return False
370 370 else:
371 371 handler(item)
372 372 return True
373 373
374 374 def _show_interpreter_prompt(self, number=None):
375 375 """ Reimplemented for IPython-style prompts.
376 376 """
377 377 # If a number was not specified, make a prompt number request.
378 378 if number is None:
379 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
379 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
380 380 info = self._ExecutionRequest(msg_id, 'prompt')
381 381 self._request_info['execute'][msg_id] = info
382 382 return
383 383
384 384 # Show a new prompt and save information about it so that it can be
385 385 # updated later if the prompt number turns out to be wrong.
386 386 self._prompt_sep = self.input_sep
387 387 self._show_prompt(self._make_in_prompt(number), html=True)
388 388 block = self._control.document().lastBlock()
389 389 length = len(self._prompt)
390 390 self._previous_prompt_obj = self._PromptBlock(block, length, number)
391 391
392 392 # Update continuation prompt to reflect (possibly) new prompt length.
393 393 self._set_continuation_prompt(
394 394 self._make_continuation_prompt(self._prompt), html=True)
395 395
396 396 def _show_interpreter_prompt_for_reply(self, msg):
397 397 """ Reimplemented for IPython-style prompts.
398 398 """
399 399 # Update the old prompt number if necessary.
400 400 content = msg['content']
401 401 # abort replies do not have any keys:
402 402 if content['status'] == 'aborted':
403 403 if self._previous_prompt_obj:
404 404 previous_prompt_number = self._previous_prompt_obj.number
405 405 else:
406 406 previous_prompt_number = 0
407 407 else:
408 408 previous_prompt_number = content['execution_count']
409 409 if self._previous_prompt_obj and \
410 410 self._previous_prompt_obj.number != previous_prompt_number:
411 411 block = self._previous_prompt_obj.block
412 412
413 413 # Make sure the prompt block has not been erased.
414 414 if block.isValid() and block.text():
415 415
416 416 # Remove the old prompt and insert a new prompt.
417 417 cursor = QtGui.QTextCursor(block)
418 418 cursor.movePosition(QtGui.QTextCursor.Right,
419 419 QtGui.QTextCursor.KeepAnchor,
420 420 self._previous_prompt_obj.length)
421 421 prompt = self._make_in_prompt(previous_prompt_number)
422 422 self._prompt = self._insert_html_fetching_plain_text(
423 423 cursor, prompt)
424 424
425 425 # When the HTML is inserted, Qt blows away the syntax
426 426 # highlighting for the line, so we need to rehighlight it.
427 427 self._highlighter.rehighlightBlock(cursor.block())
428 428
429 429 self._previous_prompt_obj = None
430 430
431 431 # Show a new prompt with the kernel's estimated prompt number.
432 432 self._show_interpreter_prompt(previous_prompt_number + 1)
433 433
434 434 #---------------------------------------------------------------------------
435 435 # 'IPythonWidget' interface
436 436 #---------------------------------------------------------------------------
437 437
438 438 def set_default_style(self, colors='lightbg'):
439 439 """ Sets the widget style to the class defaults.
440 440
441 441 Parameters:
442 442 -----------
443 443 colors : str, optional (default lightbg)
444 444 Whether to use the default IPython light background or dark
445 445 background or B&W style.
446 446 """
447 447 colors = colors.lower()
448 448 if colors=='lightbg':
449 449 self.style_sheet = styles.default_light_style_sheet
450 450 self.syntax_style = styles.default_light_syntax_style
451 451 elif colors=='linux':
452 452 self.style_sheet = styles.default_dark_style_sheet
453 453 self.syntax_style = styles.default_dark_syntax_style
454 454 elif colors=='nocolor':
455 455 self.style_sheet = styles.default_bw_style_sheet
456 456 self.syntax_style = styles.default_bw_syntax_style
457 457 else:
458 458 raise KeyError("No such color scheme: %s"%colors)
459 459
460 460 #---------------------------------------------------------------------------
461 461 # 'IPythonWidget' protected interface
462 462 #---------------------------------------------------------------------------
463 463
464 464 def _edit(self, filename, line=None):
465 465 """ Opens a Python script for editing.
466 466
467 467 Parameters:
468 468 -----------
469 469 filename : str
470 470 A path to a local system file.
471 471
472 472 line : int, optional
473 473 A line of interest in the file.
474 474 """
475 475 if self.custom_edit:
476 476 self.custom_edit_requested.emit(filename, line)
477 477 elif not self.editor:
478 478 self._append_plain_text('No default editor available.\n'
479 479 'Specify a GUI text editor in the `IPythonWidget.editor` '
480 480 'configurable to enable the %edit magic')
481 481 else:
482 482 try:
483 483 filename = '"%s"' % filename
484 484 if line and self.editor_line:
485 485 command = self.editor_line.format(filename=filename,
486 486 line=line)
487 487 else:
488 488 try:
489 489 command = self.editor.format()
490 490 except KeyError:
491 491 command = self.editor.format(filename=filename)
492 492 else:
493 493 command += ' ' + filename
494 494 except KeyError:
495 495 self._append_plain_text('Invalid editor command.\n')
496 496 else:
497 497 try:
498 498 Popen(command, shell=True)
499 499 except OSError:
500 500 msg = 'Opening editor with command "%s" failed.\n'
501 501 self._append_plain_text(msg % command)
502 502
503 503 def _make_in_prompt(self, number):
504 504 """ Given a prompt number, returns an HTML In prompt.
505 505 """
506 506 try:
507 507 body = self.in_prompt % number
508 508 except TypeError:
509 509 # allow in_prompt to leave out number, e.g. '>>> '
510 510 body = self.in_prompt
511 511 return '<span class="in-prompt">%s</span>' % body
512 512
513 513 def _make_continuation_prompt(self, prompt):
514 514 """ Given a plain text version of an In prompt, returns an HTML
515 515 continuation prompt.
516 516 """
517 517 end_chars = '...: '
518 518 space_count = len(prompt.lstrip('\n')) - len(end_chars)
519 519 body = '&nbsp;' * space_count + end_chars
520 520 return '<span class="in-prompt">%s</span>' % body
521 521
522 522 def _make_out_prompt(self, number):
523 523 """ Given a prompt number, returns an HTML Out prompt.
524 524 """
525 525 body = self.out_prompt % number
526 526 return '<span class="out-prompt">%s</span>' % body
527 527
528 528 #------ Payload handlers --------------------------------------------------
529 529
530 530 # Payload handlers with a generic interface: each takes the opaque payload
531 531 # dict, unpacks it and calls the underlying functions with the necessary
532 532 # arguments.
533 533
534 534 def _handle_payload_edit(self, item):
535 535 self._edit(item['filename'], item['line_number'])
536 536
537 537 def _handle_payload_exit(self, item):
538 538 self._keep_kernel_on_exit = item['keepkernel']
539 539 self.exit_requested.emit(self)
540 540
541 541 def _handle_payload_next_input(self, item):
542 542 self.input_buffer = dedent(item['text'].rstrip())
543 543
544 544 def _handle_payload_page(self, item):
545 545 # Since the plain text widget supports only a very small subset of HTML
546 546 # and we have no control over the HTML source, we only page HTML
547 547 # payloads in the rich text widget.
548 548 if item['html'] and self.kind == 'rich':
549 549 self._page(item['html'], html=True)
550 550 else:
551 551 self._page(item['text'], html=False)
552 552
553 553 #------ Trait change handlers --------------------------------------------
554 554
555 555 def _style_sheet_changed(self):
556 556 """ Set the style sheets of the underlying widgets.
557 557 """
558 558 self.setStyleSheet(self.style_sheet)
559 559 if self._control is not None:
560 560 self._control.document().setDefaultStyleSheet(self.style_sheet)
561 561 bg_color = self._control.palette().window().color()
562 562 self._ansi_processor.set_background_color(bg_color)
563 563
564 564 if self._page_control is not None:
565 565 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
566 566
567 567
568 568
569 569 def _syntax_style_changed(self):
570 570 """ Set the style for the syntax highlighter.
571 571 """
572 572 if self._highlighter is None:
573 573 # ignore premature calls
574 574 return
575 575 if self.syntax_style:
576 576 self._highlighter.set_style(self.syntax_style)
577 577 else:
578 578 self._highlighter.set_style_sheet(self.style_sheet)
579 579
580 580 #------ Trait default initializers -----------------------------------------
581 581
582 582 def _banner_default(self):
583 583 from IPython.core.usage import default_gui_banner
584 584 return default_gui_banner
@@ -1,990 +1,992
1 1 """The Qt MainWindow for the QtConsole
2 2
3 3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 4 common actions.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import sys
24 24 import re
25 25 import webbrowser
26 26 import ast
27 27 from threading import Thread
28 28
29 29 # System library imports
30 30 from IPython.external.qt import QtGui,QtCore
31 31
32 32 def background(f):
33 33 """call a function in a simple thread, to prevent blocking"""
34 34 t = Thread(target=f)
35 35 t.start()
36 36 return t
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Classes
40 40 #-----------------------------------------------------------------------------
41 41
42 42 class MainWindow(QtGui.QMainWindow):
43 43
44 44 #---------------------------------------------------------------------------
45 45 # 'object' interface
46 46 #---------------------------------------------------------------------------
47 47
48 48 _magic_menu_dict = {}
49 49
50 50 def __init__(self, app,
51 51 confirm_exit=True,
52 52 new_frontend_factory=None, slave_frontend_factory=None,
53 53 ):
54 54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
55 55
56 56 Parameters
57 57 ----------
58 58
59 59 app : reference to QApplication parent
60 60 confirm_exit : bool, optional
61 61 Whether we should prompt on close of tabs
62 62 new_frontend_factory : callable
63 63 A callable that returns a new IPythonWidget instance, attached to
64 64 its own running kernel.
65 65 slave_frontend_factory : callable
66 66 A callable that takes an existing IPythonWidget, and returns a new
67 67 IPythonWidget instance, attached to the same kernel.
68 68 """
69 69
70 70 super(MainWindow, self).__init__()
71 71 self._kernel_counter = 0
72 72 self._app = app
73 73 self.confirm_exit = confirm_exit
74 74 self.new_frontend_factory = new_frontend_factory
75 75 self.slave_frontend_factory = slave_frontend_factory
76 76
77 77 self.tab_widget = QtGui.QTabWidget(self)
78 78 self.tab_widget.setDocumentMode(True)
79 79 self.tab_widget.setTabsClosable(True)
80 80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
81 81
82 82 self.setCentralWidget(self.tab_widget)
83 83 # hide tab bar at first, since we have no tabs:
84 84 self.tab_widget.tabBar().setVisible(False)
85 85 # prevent focus in tab bar
86 86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
87 87
88 88 def update_tab_bar_visibility(self):
89 89 """ update visibility of the tabBar depending of the number of tab
90 90
91 91 0 or 1 tab, tabBar hidden
92 92 2+ tabs, tabBar visible
93 93
94 94 send a self.close if number of tab ==0
95 95
96 96 need to be called explicitly, or be connected to tabInserted/tabRemoved
97 97 """
98 98 if self.tab_widget.count() <= 1:
99 99 self.tab_widget.tabBar().setVisible(False)
100 100 else:
101 101 self.tab_widget.tabBar().setVisible(True)
102 102 if self.tab_widget.count()==0 :
103 103 self.close()
104 104
105 105 @property
106 106 def next_kernel_id(self):
107 107 """constantly increasing counter for kernel IDs"""
108 108 c = self._kernel_counter
109 109 self._kernel_counter += 1
110 110 return c
111 111
112 112 @property
113 113 def active_frontend(self):
114 114 return self.tab_widget.currentWidget()
115 115
116 116 def create_tab_with_new_frontend(self):
117 117 """create a new frontend and attach it to a new tab"""
118 118 widget = self.new_frontend_factory()
119 119 self.add_tab_with_frontend(widget)
120 120
121 121 def create_tab_with_current_kernel(self):
122 122 """create a new frontend attached to the same kernel as the current tab"""
123 123 current_widget = self.tab_widget.currentWidget()
124 124 current_widget_index = self.tab_widget.indexOf(current_widget)
125 125 current_widget_name = self.tab_widget.tabText(current_widget_index)
126 126 widget = self.slave_frontend_factory(current_widget)
127 127 if 'slave' in current_widget_name:
128 128 # don't keep stacking slaves
129 129 name = current_widget_name
130 130 else:
131 131 name = '(%s) slave' % current_widget_name
132 132 self.add_tab_with_frontend(widget,name=name)
133 133
134 134 def close_tab(self,current_tab):
135 135 """ Called when you need to try to close a tab.
136 136
137 137 It takes the number of the tab to be closed as argument, or a reference
138 138 to the widget inside this tab
139 139 """
140 140
141 141 # let's be sure "tab" and "closing widget" are respectively the index
142 142 # of the tab to close and a reference to the frontend to close
143 143 if type(current_tab) is not int :
144 144 current_tab = self.tab_widget.indexOf(current_tab)
145 145 closing_widget=self.tab_widget.widget(current_tab)
146 146
147 147
148 148 # when trying to be closed, widget might re-send a request to be
149 149 # closed again, but will be deleted when event will be processed. So
150 150 # need to check that widget still exists and skip if not. One example
151 151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
152 152 # re-sent by this function on the master widget, which ask all slave
153 153 # widgets to exit
154 154 if closing_widget==None:
155 155 return
156 156
157 157 #get a list of all slave widgets on the same kernel.
158 158 slave_tabs = self.find_slave_widgets(closing_widget)
159 159
160 160 keepkernel = None #Use the prompt by default
161 161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
162 162 keepkernel = closing_widget._keep_kernel_on_exit
163 163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
164 164 # we set local slave tabs._hidden to True to avoid prompting for kernel
165 165 # restart when they get the signal. and then "forward" the 'exit'
166 166 # to the main window
167 167 if keepkernel is not None:
168 168 for tab in slave_tabs:
169 169 tab._hidden = True
170 170 if closing_widget in slave_tabs:
171 171 try :
172 172 self.find_master_tab(closing_widget).execute('exit')
173 173 except AttributeError:
174 174 self.log.info("Master already closed or not local, closing only current tab")
175 175 self.tab_widget.removeTab(current_tab)
176 176 self.update_tab_bar_visibility()
177 177 return
178 178
179 kernel_client = closing_widget.kernel_client
179 180 kernel_manager = closing_widget.kernel_manager
180 181
181 182 if keepkernel is None and not closing_widget._confirm_exit:
182 183 # don't prompt, just terminate the kernel if we own it
183 184 # or leave it alone if we don't
184 185 keepkernel = closing_widget._existing
185 186 if keepkernel is None: #show prompt
186 if kernel_manager and kernel_manager.channels_running:
187 if kernel_client and kernel_client.channels_running:
187 188 title = self.window().windowTitle()
188 189 cancel = QtGui.QMessageBox.Cancel
189 190 okay = QtGui.QMessageBox.Ok
190 191 if closing_widget._may_close:
191 192 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
192 193 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
193 194 justthis = QtGui.QPushButton("&No, just this Tab", self)
194 195 justthis.setShortcut('N')
195 196 closeall = QtGui.QPushButton("&Yes, close all", self)
196 197 closeall.setShortcut('Y')
197 198 # allow ctrl-d ctrl-d exit, like in terminal
198 199 closeall.setShortcut('Ctrl+D')
199 200 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
200 201 title, msg)
201 202 box.setInformativeText(info)
202 203 box.addButton(cancel)
203 204 box.addButton(justthis, QtGui.QMessageBox.NoRole)
204 205 box.addButton(closeall, QtGui.QMessageBox.YesRole)
205 206 box.setDefaultButton(closeall)
206 207 box.setEscapeButton(cancel)
207 208 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
208 209 box.setIconPixmap(pixmap)
209 210 reply = box.exec_()
210 211 if reply == 1: # close All
211 212 for slave in slave_tabs:
212 background(slave.kernel_manager.stop_channels)
213 background(slave.kernel_client.stop_channels)
213 214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 215 closing_widget.execute("exit")
215 216 self.tab_widget.removeTab(current_tab)
216 background(kernel_manager.stop_channels)
217 background(kernel_client.stop_channels)
217 218 elif reply == 0: # close Console
218 219 if not closing_widget._existing:
219 220 # Have kernel: don't quit, just close the tab
220 221 closing_widget.execute("exit True")
221 222 self.tab_widget.removeTab(current_tab)
222 background(kernel_manager.stop_channels)
223 background(kernel_client.stop_channels)
223 224 else:
224 225 reply = QtGui.QMessageBox.question(self, title,
225 226 "Are you sure you want to close this Console?"+
226 227 "\nThe Kernel and other Consoles will remain active.",
227 228 okay|cancel,
228 229 defaultButton=okay
229 230 )
230 231 if reply == okay:
231 232 self.tab_widget.removeTab(current_tab)
232 233 elif keepkernel: #close console but leave kernel running (no prompt)
233 234 self.tab_widget.removeTab(current_tab)
234 background(kernel_manager.stop_channels)
235 background(kernel_client.stop_channels)
235 236 else: #close console and kernel (no prompt)
236 237 self.tab_widget.removeTab(current_tab)
237 if kernel_manager and kernel_manager.channels_running:
238 if kernel_client and kernel_client.channels_running:
238 239 for slave in slave_tabs:
239 background(slave.kernel_manager.stop_channels)
240 background(slave.kernel_client.stop_channels)
240 241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
242 if kernel_manager:
241 243 kernel_manager.shutdown_kernel()
242 background(kernel_manager.stop_channels)
244 background(kernel_client.stop_channels)
243 245
244 246 self.update_tab_bar_visibility()
245 247
246 248 def add_tab_with_frontend(self,frontend,name=None):
247 249 """ insert a tab with a given frontend in the tab bar, and give it a name
248 250
249 251 """
250 252 if not name:
251 253 name = 'kernel %i' % self.next_kernel_id
252 254 self.tab_widget.addTab(frontend,name)
253 255 self.update_tab_bar_visibility()
254 256 self.make_frontend_visible(frontend)
255 257 frontend.exit_requested.connect(self.close_tab)
256 258
257 259 def next_tab(self):
258 260 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
259 261
260 262 def prev_tab(self):
261 263 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
262 264
263 265 def make_frontend_visible(self,frontend):
264 266 widget_index=self.tab_widget.indexOf(frontend)
265 267 if widget_index > 0 :
266 268 self.tab_widget.setCurrentIndex(widget_index)
267 269
268 270 def find_master_tab(self,tab,as_list=False):
269 271 """
270 272 Try to return the frontend that owns the kernel attached to the given widget/tab.
271 273
272 274 Only finds frontend owned by the current application. Selection
273 275 based on port of the kernel might be inaccurate if several kernel
274 276 on different ip use same port number.
275 277
276 278 This function does the conversion tabNumber/widget if needed.
277 279 Might return None if no master widget (non local kernel)
278 280 Will crash IPython if more than 1 masterWidget
279 281
280 282 When asList set to True, always return a list of widget(s) owning
281 283 the kernel. The list might be empty or containing several Widget.
282 284 """
283 285
284 286 #convert from/to int/richIpythonWidget if needed
285 287 if isinstance(tab, int):
286 288 tab = self.tab_widget.widget(tab)
287 km=tab.kernel_manager
289 km=tab.kernel_client
288 290
289 291 #build list of all widgets
290 292 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
291 293
292 294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 295 # And should have a _may_close attribute
294 296 filtered_widget_list = [ widget for widget in widget_list if
295 widget.kernel_manager.connection_file == km.connection_file and
297 widget.kernel_client.connection_file == km.connection_file and
296 298 hasattr(widget,'_may_close') ]
297 299 # the master widget is the one that may close the kernel
298 300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
299 301 if as_list:
300 302 return master_widget
301 303 assert(len(master_widget)<=1 )
302 304 if len(master_widget)==0:
303 305 return None
304 306
305 307 return master_widget[0]
306 308
307 309 def find_slave_widgets(self,tab):
308 310 """return all the frontends that do not own the kernel attached to the given widget/tab.
309 311
310 312 Only find frontends owned by the current application. Selection
311 313 based on connection file of the kernel.
312 314
313 315 This function does the conversion tabNumber/widget if needed.
314 316 """
315 317 #convert from/to int/richIpythonWidget if needed
316 318 if isinstance(tab, int):
317 319 tab = self.tab_widget.widget(tab)
318 km=tab.kernel_manager
320 km=tab.kernel_client
319 321
320 322 #build list of all widgets
321 323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322 324
323 325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 326 filtered_widget_list = ( widget for widget in widget_list if
325 widget.kernel_manager.connection_file == km.connection_file)
327 widget.kernel_client.connection_file == km.connection_file)
326 328 # Get a list of all widget owning the same kernel and removed it from
327 329 # the previous cadidate. (better using sets ?)
328 330 master_widget_list = self.find_master_tab(tab, as_list=True)
329 331 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
330 332
331 333 return slave_list
332 334
333 335 # Populate the menu bar with common actions and shortcuts
334 336 def add_menu_action(self, menu, action, defer_shortcut=False):
335 337 """Add action to menu as well as self
336 338
337 339 So that when the menu bar is invisible, its actions are still available.
338 340
339 341 If defer_shortcut is True, set the shortcut context to widget-only,
340 342 where it will avoid conflict with shortcuts already bound to the
341 343 widgets themselves.
342 344 """
343 345 menu.addAction(action)
344 346 self.addAction(action)
345 347
346 348 if defer_shortcut:
347 349 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
348 350
349 351 def init_menu_bar(self):
350 352 #create menu in the order they should appear in the menu bar
351 353 self.init_file_menu()
352 354 self.init_edit_menu()
353 355 self.init_view_menu()
354 356 self.init_kernel_menu()
355 357 self.init_magic_menu()
356 358 self.init_window_menu()
357 359 self.init_help_menu()
358 360
359 361 def init_file_menu(self):
360 362 self.file_menu = self.menuBar().addMenu("&File")
361 363
362 364 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
363 365 self,
364 366 shortcut="Ctrl+T",
365 367 triggered=self.create_tab_with_new_frontend)
366 368 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
367 369
368 370 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
369 371 self,
370 372 shortcut="Ctrl+Shift+T",
371 373 triggered=self.create_tab_with_current_kernel)
372 374 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
373 375
374 376 self.file_menu.addSeparator()
375 377
376 378 self.close_action=QtGui.QAction("&Close Tab",
377 379 self,
378 380 shortcut=QtGui.QKeySequence.Close,
379 381 triggered=self.close_active_frontend
380 382 )
381 383 self.add_menu_action(self.file_menu, self.close_action)
382 384
383 385 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
384 386 self,
385 387 shortcut=QtGui.QKeySequence.Save,
386 388 triggered=self.export_action_active_frontend
387 389 )
388 390 self.add_menu_action(self.file_menu, self.export_action, True)
389 391
390 392 self.file_menu.addSeparator()
391 393
392 394 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
393 395 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
394 396 # Only override the default if there is a collision.
395 397 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
396 398 printkey = "Ctrl+Shift+P"
397 399 self.print_action = QtGui.QAction("&Print",
398 400 self,
399 401 shortcut=printkey,
400 402 triggered=self.print_action_active_frontend)
401 403 self.add_menu_action(self.file_menu, self.print_action, True)
402 404
403 405 if sys.platform != 'darwin':
404 406 # OSX always has Quit in the Application menu, only add it
405 407 # to the File menu elsewhere.
406 408
407 409 self.file_menu.addSeparator()
408 410
409 411 self.quit_action = QtGui.QAction("&Quit",
410 412 self,
411 413 shortcut=QtGui.QKeySequence.Quit,
412 414 triggered=self.close,
413 415 )
414 416 self.add_menu_action(self.file_menu, self.quit_action)
415 417
416 418
417 419 def init_edit_menu(self):
418 420 self.edit_menu = self.menuBar().addMenu("&Edit")
419 421
420 422 self.undo_action = QtGui.QAction("&Undo",
421 423 self,
422 424 shortcut=QtGui.QKeySequence.Undo,
423 425 statusTip="Undo last action if possible",
424 426 triggered=self.undo_active_frontend
425 427 )
426 428 self.add_menu_action(self.edit_menu, self.undo_action)
427 429
428 430 self.redo_action = QtGui.QAction("&Redo",
429 431 self,
430 432 shortcut=QtGui.QKeySequence.Redo,
431 433 statusTip="Redo last action if possible",
432 434 triggered=self.redo_active_frontend)
433 435 self.add_menu_action(self.edit_menu, self.redo_action)
434 436
435 437 self.edit_menu.addSeparator()
436 438
437 439 self.cut_action = QtGui.QAction("&Cut",
438 440 self,
439 441 shortcut=QtGui.QKeySequence.Cut,
440 442 triggered=self.cut_active_frontend
441 443 )
442 444 self.add_menu_action(self.edit_menu, self.cut_action, True)
443 445
444 446 self.copy_action = QtGui.QAction("&Copy",
445 447 self,
446 448 shortcut=QtGui.QKeySequence.Copy,
447 449 triggered=self.copy_active_frontend
448 450 )
449 451 self.add_menu_action(self.edit_menu, self.copy_action, True)
450 452
451 453 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
452 454 self,
453 455 shortcut="Ctrl+Shift+C",
454 456 triggered=self.copy_raw_active_frontend
455 457 )
456 458 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
457 459
458 460 self.paste_action = QtGui.QAction("&Paste",
459 461 self,
460 462 shortcut=QtGui.QKeySequence.Paste,
461 463 triggered=self.paste_active_frontend
462 464 )
463 465 self.add_menu_action(self.edit_menu, self.paste_action, True)
464 466
465 467 self.edit_menu.addSeparator()
466 468
467 469 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
468 470 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
469 471 # Only override the default if there is a collision.
470 472 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
471 473 selectall = "Ctrl+Shift+A"
472 474 self.select_all_action = QtGui.QAction("Select &All",
473 475 self,
474 476 shortcut=selectall,
475 477 triggered=self.select_all_active_frontend
476 478 )
477 479 self.add_menu_action(self.edit_menu, self.select_all_action, True)
478 480
479 481
480 482 def init_view_menu(self):
481 483 self.view_menu = self.menuBar().addMenu("&View")
482 484
483 485 if sys.platform != 'darwin':
484 486 # disable on OSX, where there is always a menu bar
485 487 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
486 488 self,
487 489 shortcut="Ctrl+Shift+M",
488 490 statusTip="Toggle visibility of menubar",
489 491 triggered=self.toggle_menu_bar)
490 492 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
491 493
492 494 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
493 495 self.full_screen_act = QtGui.QAction("&Full Screen",
494 496 self,
495 497 shortcut=fs_key,
496 498 statusTip="Toggle between Fullscreen and Normal Size",
497 499 triggered=self.toggleFullScreen)
498 500 self.add_menu_action(self.view_menu, self.full_screen_act)
499 501
500 502 self.view_menu.addSeparator()
501 503
502 504 self.increase_font_size = QtGui.QAction("Zoom &In",
503 505 self,
504 506 shortcut=QtGui.QKeySequence.ZoomIn,
505 507 triggered=self.increase_font_size_active_frontend
506 508 )
507 509 self.add_menu_action(self.view_menu, self.increase_font_size, True)
508 510
509 511 self.decrease_font_size = QtGui.QAction("Zoom &Out",
510 512 self,
511 513 shortcut=QtGui.QKeySequence.ZoomOut,
512 514 triggered=self.decrease_font_size_active_frontend
513 515 )
514 516 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
515 517
516 518 self.reset_font_size = QtGui.QAction("Zoom &Reset",
517 519 self,
518 520 shortcut="Ctrl+0",
519 521 triggered=self.reset_font_size_active_frontend
520 522 )
521 523 self.add_menu_action(self.view_menu, self.reset_font_size, True)
522 524
523 525 self.view_menu.addSeparator()
524 526
525 527 self.clear_action = QtGui.QAction("&Clear Screen",
526 528 self,
527 529 shortcut='Ctrl+L',
528 530 statusTip="Clear the console",
529 531 triggered=self.clear_magic_active_frontend)
530 532 self.add_menu_action(self.view_menu, self.clear_action)
531 533
532 534 self.pager_menu = self.view_menu.addMenu("&Pager")
533 535
534 536 hsplit_action = QtGui.QAction(".. &Horizontal Split",
535 537 self,
536 538 triggered=lambda: self.set_paging_active_frontend('hsplit'))
537 539
538 540 vsplit_action = QtGui.QAction(" : &Vertical Split",
539 541 self,
540 542 triggered=lambda: self.set_paging_active_frontend('vsplit'))
541 543
542 544 inside_action = QtGui.QAction(" &Inside Pager",
543 545 self,
544 546 triggered=lambda: self.set_paging_active_frontend('inside'))
545 547
546 548 self.pager_menu.addAction(hsplit_action)
547 549 self.pager_menu.addAction(vsplit_action)
548 550 self.pager_menu.addAction(inside_action)
549 551
550 552 def init_kernel_menu(self):
551 553 self.kernel_menu = self.menuBar().addMenu("&Kernel")
552 554 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
553 555 # keep the signal shortcuts to ctrl, rather than
554 556 # platform-default like we do elsewhere.
555 557
556 558 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
557 559
558 560 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
559 561 self,
560 562 triggered=self.interrupt_kernel_active_frontend,
561 563 shortcut=ctrl+"+C",
562 564 )
563 565 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
564 566
565 567 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
566 568 self,
567 569 triggered=self.restart_kernel_active_frontend,
568 570 shortcut=ctrl+"+.",
569 571 )
570 572 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
571 573
572 574 self.kernel_menu.addSeparator()
573 575
574 576 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
575 577 self,
576 578 checkable=True,
577 579 checked=self.active_frontend.confirm_restart,
578 580 triggered=self.toggle_confirm_restart_active_frontend
579 581 )
580 582
581 583 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
582 584 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
583 585
584 586 def _make_dynamic_magic(self,magic):
585 587 """Return a function `fun` that will execute `magic` on active frontend.
586 588
587 589 Parameters
588 590 ----------
589 591 magic : string
590 592 string that will be executed as is when the returned function is called
591 593
592 594 Returns
593 595 -------
594 596 fun : function
595 597 function with no parameters, when called will execute `magic` on the
596 598 current active frontend at call time
597 599
598 600 See Also
599 601 --------
600 602 populate_all_magic_menu : generate the "All Magics..." menu
601 603
602 604 Notes
603 605 -----
604 606 `fun` executes `magic` in active frontend at the moment it is triggered,
605 607 not the active frontend at the moment it was created.
606 608
607 609 This function is mostly used to create the "All Magics..." Menu at run time.
608 610 """
609 611 # need two level nested function to be sure to pass magic
610 612 # to active frontend **at run time**.
611 613 def inner_dynamic_magic():
612 614 self.active_frontend.execute(magic)
613 615 inner_dynamic_magic.__name__ = "dynamics_magic_s"
614 616 return inner_dynamic_magic
615 617
616 618 def populate_all_magic_menu(self, listofmagic=None):
617 619 """Clean "All Magics..." menu and repopulate it with `listofmagic`
618 620
619 621 Parameters
620 622 ----------
621 623 listofmagic : string,
622 624 repr() of a list of strings, send back by the kernel
623 625
624 626 Notes
625 627 -----
626 628 `listofmagic`is a repr() of list because it is fed with the result of
627 629 a 'user_expression'
628 630 """
629 631 for k,v in self._magic_menu_dict.items():
630 632 v.clear()
631 633 self.all_magic_menu.clear()
632 634
633 635
634 636 mlist=ast.literal_eval(listofmagic)
635 637 for magic in mlist:
636 638 cell = (magic['type'] == 'cell')
637 639 name = magic['name']
638 640 mclass = magic['class']
639 641 if cell :
640 642 prefix='%%'
641 643 else :
642 644 prefix='%'
643 645 magic_menu = self._get_magic_menu(mclass)
644 646
645 647 pmagic = '%s%s'%(prefix,name)
646 648
647 649 xaction = QtGui.QAction(pmagic,
648 650 self,
649 651 triggered=self._make_dynamic_magic(pmagic)
650 652 )
651 653 magic_menu.addAction(xaction)
652 654 self.all_magic_menu.addAction(xaction)
653 655
654 656 def update_all_magic_menu(self):
655 657 """ Update the list of magics in the "All Magics..." Menu
656 658
657 659 Request the kernel with the list of available magics and populate the
658 660 menu with the list received back
659 661
660 662 """
661 663 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
662 664 self.populate_all_magic_menu)
663 665
664 666 def _get_magic_menu(self,menuidentifier, menulabel=None):
665 667 """return a submagic menu by name, and create it if needed
666 668
667 669 parameters:
668 670 -----------
669 671
670 672 menulabel : str
671 673 Label for the menu
672 674
673 675 Will infere the menu name from the identifier at creation if menulabel not given.
674 676 To do so you have too give menuidentifier as a CamelCassedString
675 677 """
676 678 menu = self._magic_menu_dict.get(menuidentifier,None)
677 679 if not menu :
678 680 if not menulabel:
679 681 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
680 682 menu = QtGui.QMenu(menulabel,self.magic_menu)
681 683 self._magic_menu_dict[menuidentifier]=menu
682 684 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
683 685 return menu
684 686
685 687
686 688
687 689 def init_magic_menu(self):
688 690 self.magic_menu = self.menuBar().addMenu("&Magic")
689 691 self.magic_menu_separator = self.magic_menu.addSeparator()
690 692
691 693 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
692 694
693 695 # This action should usually not appear as it will be cleared when menu
694 696 # is updated at first kernel response. Though, it is necessary when
695 697 # connecting through X-forwarding, as in this case, the menu is not
696 698 # auto updated, SO DO NOT DELETE.
697 699 self.pop = QtGui.QAction("&Update All Magic Menu ",
698 700 self, triggered=self.update_all_magic_menu)
699 701 self.add_menu_action(self.all_magic_menu, self.pop)
700 702 # we need to populate the 'Magic Menu' once the kernel has answer at
701 703 # least once let's do it immediately, but it's assured to works
702 704 self.pop.trigger()
703 705
704 706 self.reset_action = QtGui.QAction("&Reset",
705 707 self,
706 708 statusTip="Clear all variables from workspace",
707 709 triggered=self.reset_magic_active_frontend)
708 710 self.add_menu_action(self.magic_menu, self.reset_action)
709 711
710 712 self.history_action = QtGui.QAction("&History",
711 713 self,
712 714 statusTip="show command history",
713 715 triggered=self.history_magic_active_frontend)
714 716 self.add_menu_action(self.magic_menu, self.history_action)
715 717
716 718 self.save_action = QtGui.QAction("E&xport History ",
717 719 self,
718 720 statusTip="Export History as Python File",
719 721 triggered=self.save_magic_active_frontend)
720 722 self.add_menu_action(self.magic_menu, self.save_action)
721 723
722 724 self.who_action = QtGui.QAction("&Who",
723 725 self,
724 726 statusTip="List interactive variables",
725 727 triggered=self.who_magic_active_frontend)
726 728 self.add_menu_action(self.magic_menu, self.who_action)
727 729
728 730 self.who_ls_action = QtGui.QAction("Wh&o ls",
729 731 self,
730 732 statusTip="Return a list of interactive variables",
731 733 triggered=self.who_ls_magic_active_frontend)
732 734 self.add_menu_action(self.magic_menu, self.who_ls_action)
733 735
734 736 self.whos_action = QtGui.QAction("Who&s",
735 737 self,
736 738 statusTip="List interactive variables with details",
737 739 triggered=self.whos_magic_active_frontend)
738 740 self.add_menu_action(self.magic_menu, self.whos_action)
739 741
740 742 def init_window_menu(self):
741 743 self.window_menu = self.menuBar().addMenu("&Window")
742 744 if sys.platform == 'darwin':
743 745 # add min/maximize actions to OSX, which lacks default bindings.
744 746 self.minimizeAct = QtGui.QAction("Mini&mize",
745 747 self,
746 748 shortcut="Ctrl+m",
747 749 statusTip="Minimize the window/Restore Normal Size",
748 750 triggered=self.toggleMinimized)
749 751 # maximize is called 'Zoom' on OSX for some reason
750 752 self.maximizeAct = QtGui.QAction("&Zoom",
751 753 self,
752 754 shortcut="Ctrl+Shift+M",
753 755 statusTip="Maximize the window/Restore Normal Size",
754 756 triggered=self.toggleMaximized)
755 757
756 758 self.add_menu_action(self.window_menu, self.minimizeAct)
757 759 self.add_menu_action(self.window_menu, self.maximizeAct)
758 760 self.window_menu.addSeparator()
759 761
760 762 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
761 763 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
762 764 self,
763 765 shortcut=prev_key,
764 766 statusTip="Select previous tab",
765 767 triggered=self.prev_tab)
766 768 self.add_menu_action(self.window_menu, self.prev_tab_act)
767 769
768 770 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
769 771 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
770 772 self,
771 773 shortcut=next_key,
772 774 statusTip="Select next tab",
773 775 triggered=self.next_tab)
774 776 self.add_menu_action(self.window_menu, self.next_tab_act)
775 777
776 778 def init_help_menu(self):
777 779 # please keep the Help menu in Mac Os even if empty. It will
778 780 # automatically contain a search field to search inside menus and
779 781 # please keep it spelled in English, as long as Qt Doesn't support
780 782 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
781 783 # this search field functionality
782 784
783 785 self.help_menu = self.menuBar().addMenu("&Help")
784 786
785 787
786 788 # Help Menu
787 789
788 790 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
789 791 self,
790 792 triggered=self.intro_active_frontend
791 793 )
792 794 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
793 795
794 796 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
795 797 self,
796 798 triggered=self.quickref_active_frontend
797 799 )
798 800 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
799 801
800 802 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
801 803 self,
802 804 triggered=self.guiref_active_frontend
803 805 )
804 806 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
805 807
806 808 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
807 809 self,
808 810 triggered=self._open_online_help)
809 811 self.add_menu_action(self.help_menu, self.onlineHelpAct)
810 812
811 813 # minimize/maximize/fullscreen actions:
812 814
813 815 def toggle_menu_bar(self):
814 816 menu_bar = self.menuBar()
815 817 if menu_bar.isVisible():
816 818 menu_bar.setVisible(False)
817 819 else:
818 820 menu_bar.setVisible(True)
819 821
820 822 def toggleMinimized(self):
821 823 if not self.isMinimized():
822 824 self.showMinimized()
823 825 else:
824 826 self.showNormal()
825 827
826 828 def _open_online_help(self):
827 829 filename="http://ipython.org/ipython-doc/stable/index.html"
828 830 webbrowser.open(filename, new=1, autoraise=True)
829 831
830 832 def toggleMaximized(self):
831 833 if not self.isMaximized():
832 834 self.showMaximized()
833 835 else:
834 836 self.showNormal()
835 837
836 838 # Min/Max imizing while in full screen give a bug
837 839 # when going out of full screen, at least on OSX
838 840 def toggleFullScreen(self):
839 841 if not self.isFullScreen():
840 842 self.showFullScreen()
841 843 if sys.platform == 'darwin':
842 844 self.maximizeAct.setEnabled(False)
843 845 self.minimizeAct.setEnabled(False)
844 846 else:
845 847 self.showNormal()
846 848 if sys.platform == 'darwin':
847 849 self.maximizeAct.setEnabled(True)
848 850 self.minimizeAct.setEnabled(True)
849 851
850 852 def set_paging_active_frontend(self, paging):
851 853 self.active_frontend._set_paging(paging)
852 854
853 855 def close_active_frontend(self):
854 856 self.close_tab(self.active_frontend)
855 857
856 858 def restart_kernel_active_frontend(self):
857 859 self.active_frontend.request_restart_kernel()
858 860
859 861 def interrupt_kernel_active_frontend(self):
860 862 self.active_frontend.request_interrupt_kernel()
861 863
862 864 def toggle_confirm_restart_active_frontend(self):
863 865 widget = self.active_frontend
864 866 widget.confirm_restart = not widget.confirm_restart
865 867 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
866 868
867 869 def update_restart_checkbox(self):
868 870 if self.active_frontend is None:
869 871 return
870 872 widget = self.active_frontend
871 873 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
872 874
873 875 def cut_active_frontend(self):
874 876 widget = self.active_frontend
875 877 if widget.can_cut():
876 878 widget.cut()
877 879
878 880 def copy_active_frontend(self):
879 881 widget = self.active_frontend
880 882 widget.copy()
881 883
882 884 def copy_raw_active_frontend(self):
883 885 self.active_frontend._copy_raw_action.trigger()
884 886
885 887 def paste_active_frontend(self):
886 888 widget = self.active_frontend
887 889 if widget.can_paste():
888 890 widget.paste()
889 891
890 892 def undo_active_frontend(self):
891 893 self.active_frontend.undo()
892 894
893 895 def redo_active_frontend(self):
894 896 self.active_frontend.redo()
895 897
896 898 def reset_magic_active_frontend(self):
897 899 self.active_frontend.execute("%reset")
898 900
899 901 def history_magic_active_frontend(self):
900 902 self.active_frontend.execute("%history")
901 903
902 904 def save_magic_active_frontend(self):
903 905 self.active_frontend.save_magic()
904 906
905 907 def clear_magic_active_frontend(self):
906 908 self.active_frontend.execute("%clear")
907 909
908 910 def who_magic_active_frontend(self):
909 911 self.active_frontend.execute("%who")
910 912
911 913 def who_ls_magic_active_frontend(self):
912 914 self.active_frontend.execute("%who_ls")
913 915
914 916 def whos_magic_active_frontend(self):
915 917 self.active_frontend.execute("%whos")
916 918
917 919 def print_action_active_frontend(self):
918 920 self.active_frontend.print_action.trigger()
919 921
920 922 def export_action_active_frontend(self):
921 923 self.active_frontend.export_action.trigger()
922 924
923 925 def select_all_active_frontend(self):
924 926 self.active_frontend.select_all_action.trigger()
925 927
926 928 def increase_font_size_active_frontend(self):
927 929 self.active_frontend.increase_font_size.trigger()
928 930
929 931 def decrease_font_size_active_frontend(self):
930 932 self.active_frontend.decrease_font_size.trigger()
931 933
932 934 def reset_font_size_active_frontend(self):
933 935 self.active_frontend.reset_font_size.trigger()
934 936
935 937 def guiref_active_frontend(self):
936 938 self.active_frontend.execute("%guiref")
937 939
938 940 def intro_active_frontend(self):
939 941 self.active_frontend.execute("?")
940 942
941 943 def quickref_active_frontend(self):
942 944 self.active_frontend.execute("%quickref")
943 945 #---------------------------------------------------------------------------
944 946 # QWidget interface
945 947 #---------------------------------------------------------------------------
946 948
947 949 def closeEvent(self, event):
948 950 """ Forward the close event to every tabs contained by the windows
949 951 """
950 952 if self.tab_widget.count() == 0:
951 953 # no tabs, just close
952 954 event.accept()
953 955 return
954 956 # Do Not loop on the widget count as it change while closing
955 957 title = self.window().windowTitle()
956 958 cancel = QtGui.QMessageBox.Cancel
957 959 okay = QtGui.QMessageBox.Ok
958 960
959 961 if self.confirm_exit:
960 962 if self.tab_widget.count() > 1:
961 963 msg = "Close all tabs, stop all kernels, and Quit?"
962 964 else:
963 965 msg = "Close console, stop kernel, and Quit?"
964 966 info = "Kernels not started here (e.g. notebooks) will be left alone."
965 967 closeall = QtGui.QPushButton("&Quit", self)
966 968 closeall.setShortcut('Q')
967 969 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
968 970 title, msg)
969 971 box.setInformativeText(info)
970 972 box.addButton(cancel)
971 973 box.addButton(closeall, QtGui.QMessageBox.YesRole)
972 974 box.setDefaultButton(closeall)
973 975 box.setEscapeButton(cancel)
974 976 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
975 977 box.setIconPixmap(pixmap)
976 978 reply = box.exec_()
977 979 else:
978 980 reply = okay
979 981
980 982 if reply == cancel:
981 983 event.ignore()
982 984 return
983 985 if reply == okay:
984 986 while self.tab_widget.count() >= 1:
985 987 # prevent further confirmations:
986 988 widget = self.active_frontend
987 989 widget._confirm_exit = False
988 990 self.close_tab(widget)
989 991 event.accept()
990 992
@@ -1,370 +1,375
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 import json
24 23 import os
25 24 import signal
26 25 import sys
27 import uuid
28 26
29 27 # If run on Windows, install an exception hook which pops up a
30 28 # message box. Pythonw.exe hides the console, so without this
31 29 # the application silently fails to load.
32 30 #
33 31 # We always install this handler, because the expectation is for
34 32 # qtconsole to bring up a GUI even if called from the console.
35 33 # The old handler is called, so the exception is printed as well.
36 34 # If desired, check for pythonw with an additional condition
37 35 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 36 if os.name == 'nt':
39 37 old_excepthook = sys.excepthook
40 38
41 39 def gui_excepthook(exctype, value, tb):
42 40 try:
43 41 import ctypes, traceback
44 42 MB_ICONERROR = 0x00000010L
45 43 title = u'Error starting IPython QtConsole'
46 44 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 46 finally:
49 47 # Also call the old exception hook to let it do
50 48 # its thing too.
51 49 old_excepthook(exctype, value, tb)
52 50
53 51 sys.excepthook = gui_excepthook
54 52
55 53 # System library imports
56 54 from IPython.external.qt import QtCore, QtGui
57 55
58 56 # Local imports
59 57 from IPython.config.application import boolean_flag, catch_config_error
60 58 from IPython.core.application import BaseIPythonApplication
61 59 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 60 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 61 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 62 from IPython.frontend.qt.console import styles
66 63 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
64 from IPython.frontend.qt.client import QtKernelClient
65 from IPython.frontend.qt.manager import QtKernelManager
68 66 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
71 67 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
68 Dict, List, Unicode, CBool, Any
73 69 )
74 from IPython.kernel.zmq.kernelapp import IPKernelApp
75 from IPython.kernel.zmq.session import Session, default_secure
76 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
70 from IPython.kernel.zmq.session import default_secure
77 71
78 72 from IPython.frontend.consoleapp import (
79 73 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 74 )
81 75
82 76 #-----------------------------------------------------------------------------
83 77 # Network Constants
84 78 #-----------------------------------------------------------------------------
85 79
86 80 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87 81
88 82 #-----------------------------------------------------------------------------
89 83 # Globals
90 84 #-----------------------------------------------------------------------------
91 85
92 86 _examples = """
93 87 ipython qtconsole # start the qtconsole
94 88 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 89 """
96 90
97 91 #-----------------------------------------------------------------------------
98 92 # Aliases and Flags
99 93 #-----------------------------------------------------------------------------
100 94
101 95 # start with copy of flags
102 96 flags = dict(flags)
103 97 qt_flags = {
104 98 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 99 "Disable rich text support."),
106 100 }
107 101
108 102 # and app_flags from the Console Mixin
109 103 qt_flags.update(app_flags)
110 104 # add frontend flags to the full set
111 105 flags.update(qt_flags)
112 106
113 107 # start with copy of front&backend aliases list
114 108 aliases = dict(aliases)
115 109 qt_aliases = dict(
116 110 style = 'IPythonWidget.syntax_style',
117 111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 112 colors = 'ZMQInteractiveShell.colors',
119 113
120 114 editor = 'IPythonWidget.editor',
121 115 paging = 'ConsoleWidget.paging',
122 116 )
123 117 # and app_aliases from the Console Mixin
124 118 qt_aliases.update(app_aliases)
125 119 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 120 # add frontend aliases to the full set
127 121 aliases.update(qt_aliases)
128 122
129 123 # get flags&aliases into sets, and remove a couple that
130 124 # shouldn't be scrubbed from backend flags:
131 125 qt_aliases = set(qt_aliases.keys())
132 126 qt_aliases.remove('colors')
133 127 qt_flags = set(qt_flags.keys())
134 128
135 129 #-----------------------------------------------------------------------------
136 130 # Classes
137 131 #-----------------------------------------------------------------------------
138 132
139 133 #-----------------------------------------------------------------------------
140 134 # IPythonQtConsole
141 135 #-----------------------------------------------------------------------------
142 136
143 137
144 138 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 139 name = 'ipython-qtconsole'
146 140
147 141 description = """
148 142 The IPython QtConsole.
149 143
150 144 This launches a Console-style application using Qt. It is not a full
151 145 console, in that launched terminal subprocesses will not be able to accept
152 146 input.
153 147
154 148 The QtConsole supports various extra features beyond the Terminal IPython
155 149 shell, such as inline plotting with matplotlib, via:
156 150
157 151 ipython qtconsole --pylab=inline
158 152
159 153 as well as saving your session as HTML, and printing the output.
160 154
161 155 """
162 156 examples = _examples
163 157
164 158 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 159 flags = Dict(flags)
166 160 aliases = Dict(aliases)
167 161 frontend_flags = Any(qt_flags)
168 162 frontend_aliases = Any(qt_aliases)
163 kernel_client_class = QtKernelClient
169 164 kernel_manager_class = QtKernelManager
170 165
171 166 stylesheet = Unicode('', config=True,
172 167 help="path to a custom CSS stylesheet")
173 168
174 169 plain = CBool(False, config=True,
175 170 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176 171
177 172 def _plain_changed(self, name, old, new):
178 173 kind = 'plain' if new else 'rich'
179 174 self.config.ConsoleWidget.kind = kind
180 175 if new:
181 176 self.widget_factory = IPythonWidget
182 177 else:
183 178 self.widget_factory = RichIPythonWidget
184 179
185 180 # the factory for creating a widget
186 181 widget_factory = Any(RichIPythonWidget)
187 182
188 183 def parse_command_line(self, argv=None):
189 184 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 185 self.build_kernel_argv(argv)
191 186
192 187
193 188 def new_frontend_master(self):
194 189 """ Create and return new frontend attached to new kernel, launched on localhost.
195 190 """
196 191 kernel_manager = self.kernel_manager_class(
197 192 connection_file=self._new_connection_file(),
198 193 config=self.config,
194 autorestart=True,
199 195 )
200 196 # start the kernel
201 197 kwargs = dict()
202 198 kwargs['extra_arguments'] = self.kernel_argv
203 199 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
200 kernel_manager.client_factory = self.kernel_client_class
201 kernel_client = kernel_manager.client()
202 kernel_client.start_channels(shell=True, iopub=True)
205 203 widget = self.widget_factory(config=self.config,
206 204 local_kernel=True)
207 205 self.init_colors(widget)
208 206 widget.kernel_manager = kernel_manager
207 widget.kernel_client = kernel_client
209 208 widget._existing = False
210 209 widget._may_close = True
211 210 widget._confirm_exit = self.confirm_exit
212 211 return widget
213 212
214 213 def new_frontend_slave(self, current_widget):
215 214 """Create and return a new frontend attached to an existing kernel.
216 215
217 216 Parameters
218 217 ----------
219 218 current_widget : IPythonWidget
220 219 The IPythonWidget whose kernel this frontend is to share
221 220 """
222 kernel_manager = self.kernel_manager_class(
223 connection_file=current_widget.kernel_manager.connection_file,
221 kernel_client = self.kernel_client_class(
222 connection_file=current_widget.kernel_client.connection_file,
224 223 config = self.config,
225 224 )
226 kernel_manager.load_connection_file()
227 kernel_manager.start_channels()
225 kernel_client.load_connection_file()
226 kernel_client.start_channels()
228 227 widget = self.widget_factory(config=self.config,
229 228 local_kernel=False)
230 229 self.init_colors(widget)
231 230 widget._existing = True
232 231 widget._may_close = False
233 232 widget._confirm_exit = False
234 widget.kernel_manager = kernel_manager
233 widget.kernel_client = kernel_client
234 widget.kernel_manager = current_widget.kernel_manager
235 235 return widget
236 236
237 def init_qt_app(self):
238 # separate from qt_elements, because it must run first
239 self.app = QtGui.QApplication([])
240
237 241 def init_qt_elements(self):
238 242 # Create the widget.
239 self.app = QtGui.QApplication([])
240 243
241 244 base_path = os.path.abspath(os.path.dirname(__file__))
242 245 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 246 self.app.icon = QtGui.QIcon(icon_path)
244 247 QtGui.QApplication.setWindowIcon(self.app.icon)
245 248
246 249 try:
247 250 ip = self.config.KernelManager.ip
248 251 except AttributeError:
249 252 ip = LOCALHOST
250 253 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 254 self.widget = self.widget_factory(config=self.config,
252 255 local_kernel=local_kernel)
253 256 self.init_colors(self.widget)
254 257 self.widget._existing = self.existing
255 258 self.widget._may_close = not self.existing
256 259 self.widget._confirm_exit = self.confirm_exit
257 260
258 261 self.widget.kernel_manager = self.kernel_manager
262 self.widget.kernel_client = self.kernel_client
259 263 self.window = MainWindow(self.app,
260 264 confirm_exit=self.confirm_exit,
261 265 new_frontend_factory=self.new_frontend_master,
262 266 slave_frontend_factory=self.new_frontend_slave,
263 267 )
264 268 self.window.log = self.log
265 269 self.window.add_tab_with_frontend(self.widget)
266 270 self.window.init_menu_bar()
267 271
268 272 self.window.setWindowTitle('IPython')
269 273
270 274 def init_colors(self, widget):
271 275 """Configure the coloring of the widget"""
272 276 # Note: This will be dramatically simplified when colors
273 277 # are removed from the backend.
274 278
275 279 # parse the colors arg down to current known labels
276 280 try:
277 281 colors = self.config.ZMQInteractiveShell.colors
278 282 except AttributeError:
279 283 colors = None
280 284 try:
281 285 style = self.config.IPythonWidget.syntax_style
282 286 except AttributeError:
283 287 style = None
284 288 try:
285 289 sheet = self.config.IPythonWidget.style_sheet
286 290 except AttributeError:
287 291 sheet = None
288 292
289 293 # find the value for colors:
290 294 if colors:
291 295 colors=colors.lower()
292 296 if colors in ('lightbg', 'light'):
293 297 colors='lightbg'
294 298 elif colors in ('dark', 'linux'):
295 299 colors='linux'
296 300 else:
297 301 colors='nocolor'
298 302 elif style:
299 303 if style=='bw':
300 304 colors='nocolor'
301 305 elif styles.dark_style(style):
302 306 colors='linux'
303 307 else:
304 308 colors='lightbg'
305 309 else:
306 310 colors=None
307 311
308 312 # Configure the style
309 313 if style:
310 314 widget.style_sheet = styles.sheet_from_template(style, colors)
311 315 widget.syntax_style = style
312 316 widget._syntax_style_changed()
313 317 widget._style_sheet_changed()
314 318 elif colors:
315 319 # use a default dark/light/bw style
316 320 widget.set_default_style(colors=colors)
317 321
318 322 if self.stylesheet:
319 323 # we got an explicit stylesheet
320 324 if os.path.isfile(self.stylesheet):
321 325 with open(self.stylesheet) as f:
322 326 sheet = f.read()
323 327 else:
324 328 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 329 if sheet:
326 330 widget.style_sheet = sheet
327 331 widget._style_sheet_changed()
328 332
329 333
330 334 def init_signal(self):
331 335 """allow clean shutdown on sigint"""
332 336 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 337 # need a timer, so that QApplication doesn't block until a real
334 338 # Qt event fires (can require mouse movement)
335 339 # timer trick from http://stackoverflow.com/q/4938723/938949
336 340 timer = QtCore.QTimer()
337 341 # Let the interpreter run each 200 ms:
338 342 timer.timeout.connect(lambda: None)
339 343 timer.start(200)
340 344 # hold onto ref, so the timer doesn't get cleaned up
341 345 self._sigint_timer = timer
342 346
343 347 @catch_config_error
344 348 def initialize(self, argv=None):
349 self.init_qt_app()
345 350 super(IPythonQtConsoleApp, self).initialize(argv)
346 351 IPythonConsoleApp.initialize(self,argv)
347 352 self.init_qt_elements()
348 353 self.init_signal()
349 354
350 355 def start(self):
351 356
352 357 # draw the window
353 358 self.window.show()
354 359 self.window.raise_()
355 360
356 361 # Start the application main loop.
357 362 self.app.exec_()
358 363
359 364 #-----------------------------------------------------------------------------
360 365 # Main entry point
361 366 #-----------------------------------------------------------------------------
362 367
363 368 def main():
364 369 app = IPythonQtConsoleApp()
365 370 app.initialize()
366 371 app.start()
367 372
368 373
369 374 if __name__ == '__main__':
370 375 main()
@@ -1,33 +1,40
1 1 """ Defines an in-process KernelManager with signals and slots.
2 2 """
3 3
4 4 # Local imports.
5 from IPython.kernel.inprocess.kernelmanager import \
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel, \
7 InProcessHBChannel, InProcessKernelManager
5 from IPython.kernel.inprocess import (
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel,
7 InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
8 )
9
8 10 from IPython.utils.traitlets import Type
9 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
10 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
11 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin, QtKernelClientMixin,
14 QtKernelManagerMixin,
15 )
11 16
12 17
13 18 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
14 19 pass
15 20
16 21 class QtInProcessIOPubChannel(QtIOPubChannelMixin, InProcessIOPubChannel):
17 22 pass
18 23
19 24 class QtInProcessStdInChannel(QtStdInChannelMixin, InProcessStdInChannel):
20 25 pass
21 26
22 27 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
23 28 pass
24 29
25
26 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
30 class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
27 31 """ An in-process KernelManager with signals and slots.
28 32 """
29 33
30 34 iopub_channel_class = Type(QtInProcessIOPubChannel)
31 35 shell_channel_class = Type(QtInProcessShellChannel)
32 36 stdin_channel_class = Type(QtInProcessStdInChannel)
33 37 hb_channel_class = Type(QtInProcessHBChannel)
38
39 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
40 client_class = __module__ + '.QtInProcessKernelClient'
@@ -1,260 +1,220
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # System library imports.
5 5 from IPython.external.qt import QtCore
6 6
7 7 # IPython imports.
8 8 from IPython.utils.traitlets import HasTraits, Type
9 9 from util import MetaQObjectHasTraits, SuperQObject
10 10
11 11
12 12 class ChannelQObject(SuperQObject):
13 13
14 14 # Emitted when the channel is started.
15 15 started = QtCore.Signal()
16 16
17 17 # Emitted when the channel is stopped.
18 18 stopped = QtCore.Signal()
19 19
20 20 #---------------------------------------------------------------------------
21 21 # Channel interface
22 22 #---------------------------------------------------------------------------
23 23
24 24 def start(self):
25 25 """ Reimplemented to emit signal.
26 26 """
27 27 super(ChannelQObject, self).start()
28 28 self.started.emit()
29 29
30 30 def stop(self):
31 31 """ Reimplemented to emit signal.
32 32 """
33 33 super(ChannelQObject, self).stop()
34 34 self.stopped.emit()
35 35
36 36 #---------------------------------------------------------------------------
37 37 # InProcessChannel interface
38 38 #---------------------------------------------------------------------------
39 39
40 40 def call_handlers_later(self, *args, **kwds):
41 41 """ Call the message handlers later.
42 42 """
43 43 do_later = lambda: self.call_handlers(*args, **kwds)
44 44 QtCore.QTimer.singleShot(0, do_later)
45 45
46 46 def process_events(self):
47 47 """ Process any pending GUI events.
48 48 """
49 49 QtCore.QCoreApplication.instance().processEvents()
50 50
51 51
52 52 class QtShellChannelMixin(ChannelQObject):
53 53
54 54 # Emitted when any message is received.
55 55 message_received = QtCore.Signal(object)
56 56
57 # Emitted when a reply has been received for the corresponding request
58 # type.
57 # Emitted when a reply has been received for the corresponding request type.
59 58 execute_reply = QtCore.Signal(object)
60 59 complete_reply = QtCore.Signal(object)
61 60 object_info_reply = QtCore.Signal(object)
62 61 history_reply = QtCore.Signal(object)
63 62
64 # Emitted when the first reply comes back.
65 first_reply = QtCore.Signal()
66
67 # Used by the first_reply signal logic to determine if a reply is the
68 # first.
69 _handlers_called = False
70
71 63 #---------------------------------------------------------------------------
72 64 # 'ShellChannel' interface
73 65 #---------------------------------------------------------------------------
74 66
75 67 def call_handlers(self, msg):
76 68 """ Reimplemented to emit signals instead of making callbacks.
77 69 """
78 70 # Emit the generic signal.
79 71 self.message_received.emit(msg)
80 72
81 73 # Emit signals for specialized message types.
82 74 msg_type = msg['header']['msg_type']
83 75 signal = getattr(self, msg_type, None)
84 76 if signal:
85 77 signal.emit(msg)
86 78
87 if not self._handlers_called:
88 self.first_reply.emit()
89 self._handlers_called = True
90
91 #---------------------------------------------------------------------------
92 # 'QtShellChannelMixin' interface
93 #---------------------------------------------------------------------------
94
95 def reset_first_reply(self):
96 """ Reset the first_reply signal to fire again on the next reply.
97 """
98 self._handlers_called = False
99
100 79
101 80 class QtIOPubChannelMixin(ChannelQObject):
102 81
103 82 # Emitted when any message is received.
104 83 message_received = QtCore.Signal(object)
105 84
106 85 # Emitted when a message of type 'stream' is received.
107 86 stream_received = QtCore.Signal(object)
108 87
109 88 # Emitted when a message of type 'pyin' is received.
110 89 pyin_received = QtCore.Signal(object)
111 90
112 91 # Emitted when a message of type 'pyout' is received.
113 92 pyout_received = QtCore.Signal(object)
114 93
115 94 # Emitted when a message of type 'pyerr' is received.
116 95 pyerr_received = QtCore.Signal(object)
117 96
118 97 # Emitted when a message of type 'display_data' is received
119 98 display_data_received = QtCore.Signal(object)
120 99
121 100 # Emitted when a crash report message is received from the kernel's
122 101 # last-resort sys.excepthook.
123 102 crash_received = QtCore.Signal(object)
124 103
125 104 # Emitted when a shutdown is noticed.
126 105 shutdown_reply_received = QtCore.Signal(object)
127 106
128 107 #---------------------------------------------------------------------------
129 108 # 'IOPubChannel' interface
130 109 #---------------------------------------------------------------------------
131 110
132 111 def call_handlers(self, msg):
133 112 """ Reimplemented to emit signals instead of making callbacks.
134 113 """
135 114 # Emit the generic signal.
136 115 self.message_received.emit(msg)
137 116 # Emit signals for specialized message types.
138 117 msg_type = msg['header']['msg_type']
139 118 signal = getattr(self, msg_type + '_received', None)
140 119 if signal:
141 120 signal.emit(msg)
142 121 elif msg_type in ('stdout', 'stderr'):
143 122 self.stream_received.emit(msg)
144 123
145 124 def flush(self):
146 125 """ Reimplemented to ensure that signals are dispatched immediately.
147 126 """
148 127 super(QtIOPubChannelMixin, self).flush()
149 128 QtCore.QCoreApplication.instance().processEvents()
150 129
151 130
152 131 class QtStdInChannelMixin(ChannelQObject):
153 132
154 133 # Emitted when any message is received.
155 134 message_received = QtCore.Signal(object)
156 135
157 136 # Emitted when an input request is received.
158 137 input_requested = QtCore.Signal(object)
159 138
160 139 #---------------------------------------------------------------------------
161 140 # 'StdInChannel' interface
162 141 #---------------------------------------------------------------------------
163 142
164 143 def call_handlers(self, msg):
165 144 """ Reimplemented to emit signals instead of making callbacks.
166 145 """
167 146 # Emit the generic signal.
168 147 self.message_received.emit(msg)
169 148
170 149 # Emit signals for specialized message types.
171 150 msg_type = msg['header']['msg_type']
172 151 if msg_type == 'input_request':
173 152 self.input_requested.emit(msg)
174 153
175 154
176 155 class QtHBChannelMixin(ChannelQObject):
177 156
178 157 # Emitted when the kernel has died.
179 158 kernel_died = QtCore.Signal(object)
180 159
181 160 #---------------------------------------------------------------------------
182 161 # 'HBChannel' interface
183 162 #---------------------------------------------------------------------------
184 163
185 164 def call_handlers(self, since_last_heartbeat):
186 165 """ Reimplemented to emit signals instead of making callbacks.
187 166 """
188 167 # Emit the generic signal.
189 168 self.kernel_died.emit(since_last_heartbeat)
190 169
191 170
171 class QtKernelRestarterMixin(HasTraits, SuperQObject):
172
173 __metaclass__ = MetaQObjectHasTraits
174 _timer = None
175
176
192 177 class QtKernelManagerMixin(HasTraits, SuperQObject):
193 """ A KernelManager that provides signals and slots.
178 """ A KernelClient that provides signals and slots.
194 179 """
195 180
196 181 __metaclass__ = MetaQObjectHasTraits
197 182
198 # Emitted when the kernel manager has started listening.
199 started_kernel = QtCore.Signal()
183 kernel_restarted = QtCore.Signal()
184
200 185
201 # Emitted when the kernel manager has started listening.
186 class QtKernelClientMixin(HasTraits, SuperQObject):
187 """ A KernelClient that provides signals and slots.
188 """
189
190 __metaclass__ = MetaQObjectHasTraits
191
192 # Emitted when the kernel client has started listening.
202 193 started_channels = QtCore.Signal()
203 194
204 # Emitted when the kernel manager has stopped listening.
195 # Emitted when the kernel client has stopped listening.
205 196 stopped_channels = QtCore.Signal()
206 197
207 198 # Use Qt-specific channel classes that emit signals.
208 199 iopub_channel_class = Type(QtIOPubChannelMixin)
209 200 shell_channel_class = Type(QtShellChannelMixin)
210 201 stdin_channel_class = Type(QtStdInChannelMixin)
211 202 hb_channel_class = Type(QtHBChannelMixin)
212 203
213 204 #---------------------------------------------------------------------------
214 # 'KernelManager' interface
205 # 'KernelClient' interface
215 206 #---------------------------------------------------------------------------
216 207
217 #------ Kernel process management ------------------------------------------
218
219 def start_kernel(self, *args, **kw):
220 """ Reimplemented for proper heartbeat management.
221 """
222 if self._shell_channel is not None:
223 self._shell_channel.reset_first_reply()
224 super(QtKernelManagerMixin, self).start_kernel(*args, **kw)
225 self.started_kernel.emit()
226
227 208 #------ Channel management -------------------------------------------------
228 209
229 210 def start_channels(self, *args, **kw):
230 211 """ Reimplemented to emit signal.
231 212 """
232 super(QtKernelManagerMixin, self).start_channels(*args, **kw)
213 super(QtKernelClientMixin, self).start_channels(*args, **kw)
233 214 self.started_channels.emit()
234 215
235 216 def stop_channels(self):
236 217 """ Reimplemented to emit signal.
237 218 """
238 super(QtKernelManagerMixin, self).stop_channels()
219 super(QtKernelClientMixin, self).stop_channels()
239 220 self.stopped_channels.emit()
240
241 @property
242 def shell_channel(self):
243 """ Reimplemented for proper heartbeat management.
244 """
245 if self._shell_channel is None:
246 self._shell_channel = super(QtKernelManagerMixin,self).shell_channel
247 self._shell_channel.first_reply.connect(self._first_reply)
248 return self._shell_channel
249
250 #---------------------------------------------------------------------------
251 # Protected interface
252 #---------------------------------------------------------------------------
253
254 def _first_reply(self):
255 """ Unpauses the heartbeat channel when the first reply is received on
256 the execute channel. Note that this will *not* start the heartbeat
257 channel if it is not already running!
258 """
259 if self._hb_channel is not None:
260 self._hb_channel.unpause()
@@ -1,154 +1,157
1 1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 * Paul Ivanov
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 import signal
17 17 import sys
18 18 import time
19 19
20 20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21 21
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 24 )
25 25 from IPython.utils.warn import warn,error
26 26
27 27 from IPython.kernel.zmq.kernelapp import IPKernelApp
28 28 from IPython.kernel.zmq.session import Session, default_secure
29 29 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
30 30 from IPython.frontend.consoleapp import (
31 31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 32 )
33 33
34 34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Globals
38 38 #-----------------------------------------------------------------------------
39 39
40 40 _examples = """
41 41 ipython console # start the ZMQ-based console
42 42 ipython console --existing # connect to an existing ipython session
43 43 """
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Flags and Aliases
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # copy flags from mixin:
50 50 flags = dict(flags)
51 51 # start with mixin frontend flags:
52 52 frontend_flags = dict(app_flags)
53 53 # add TerminalIPApp flags:
54 54 frontend_flags.update(term_flags)
55 55 # disable quick startup, as it won't propagate to the kernel anyway
56 56 frontend_flags.pop('quick')
57 57 # update full dict with frontend flags:
58 58 flags.update(frontend_flags)
59 59
60 60 # copy flags from mixin
61 61 aliases = dict(aliases)
62 62 # start with mixin frontend flags
63 63 frontend_aliases = dict(app_aliases)
64 64 # load updated frontend flags into full dict
65 65 aliases.update(frontend_aliases)
66 66
67 67 # get flags&aliases into sets, and remove a couple that
68 68 # shouldn't be scrubbed from backend flags:
69 69 frontend_aliases = set(frontend_aliases.keys())
70 70 frontend_flags = set(frontend_flags.keys())
71 71
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 79 name = "ipython-console"
80 80 """Start a terminal frontend to the IPython zmq kernel."""
81 81
82 82 description = """
83 83 The IPython terminal-based Console.
84 84
85 85 This launches a Console application inside a terminal.
86 86
87 87 The Console supports various extra features beyond the traditional
88 88 single-process Terminal IPython shell, such as connecting to an
89 89 existing ipython session, via:
90 90
91 91 ipython console --existing
92 92
93 93 where the previous session could have been created by another ipython
94 94 console, an ipython qtconsole, or by opening an ipython notebook.
95 95
96 96 """
97 97 examples = _examples
98 98
99 99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 100 flags = Dict(flags)
101 101 aliases = Dict(aliases)
102 102 frontend_aliases = Any(frontend_aliases)
103 103 frontend_flags = Any(frontend_flags)
104 104
105 105 subcommands = Dict()
106 106
107 107 def parse_command_line(self, argv=None):
108 108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 109 self.build_kernel_argv(argv)
110 110
111 111 def init_shell(self):
112 112 IPythonConsoleApp.initialize(self)
113 113 # relay sigint to kernel
114 114 signal.signal(signal.SIGINT, self.handle_sigint)
115 115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 116 display_banner=False, profile_dir=self.profile_dir,
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117 ipython_dir=self.ipython_dir,
118 manager=self.kernel_manager,
119 client=self.kernel_client,
120 )
118 121
119 122 def init_gui_pylab(self):
120 123 # no-op, because we don't want to import matplotlib in the frontend.
121 124 pass
122 125
123 126 def handle_sigint(self, *args):
124 127 if self.shell._executing:
125 if self.kernel_manager.has_kernel:
128 if self.kernel_manager:
126 129 # interrupt already gets passed to subprocess by signal handler.
127 130 # Only if we prevent that should we need to explicitly call
128 131 # interrupt_kernel, until which time, this would result in a
129 132 # double-interrupt:
130 133 # self.kernel_manager.interrupt_kernel()
131 134 pass
132 135 else:
133 136 self.shell.write_err('\n')
134 137 error("Cannot interrupt kernels we didn't start.\n")
135 138 else:
136 139 # raise the KeyboardInterrupt if we aren't waiting for execution,
137 140 # so that the interact loop advances, and prompt is redrawn, etc.
138 141 raise KeyboardInterrupt
139 142
140 143
141 144 def init_code(self):
142 145 # no-op in the frontend, code gets run in the backend
143 146 pass
144 147
145 148 def launch_new_instance():
146 149 """Create and run a full blown IPython instance"""
147 150 app = ZMQTerminalIPythonApp.instance()
148 151 app.initialize()
149 152 app.start()
150 153
151 154
152 155 if __name__ == '__main__':
153 156 launch_new_instance()
154 157
@@ -1,44 +1,44
1 1 # -*- coding: utf-8 -*-
2 2 import readline
3 3 from Queue import Empty
4 4
5 5 class ZMQCompleter(object):
6 6 """Client-side completion machinery.
7 7
8 8 How it works: self.complete will be called multiple times, with
9 9 state=0,1,2,... When state=0 it should compute ALL the completion matches,
10 10 and then return them for each value of state."""
11 11
12 def __init__(self, shell, km):
12 def __init__(self, shell, client):
13 13 self.shell = shell
14 self.km = km
14 self.client = client
15 15 self.matches = []
16 16
17 17 def complete_request(self,text):
18 18 line = readline.get_line_buffer()
19 19 cursor_pos = readline.get_endidx()
20 20
21 21 # send completion request to kernel
22 22 # Give the kernel up to 0.5s to respond
23 msg_id = self.km.shell_channel.complete(text=text, line=line,
23 msg_id = self.client.shell_channel.complete(text=text, line=line,
24 24 cursor_pos=cursor_pos)
25 25
26 msg = self.km.shell_channel.get_msg(timeout=0.5)
26 msg = self.client.shell_channel.get_msg(timeout=0.5)
27 27 if msg['parent_header']['msg_id'] == msg_id:
28 28 return msg["content"]["matches"]
29 29 return []
30 30
31 31 def rlcomplete(self, text, state):
32 32 if state == 0:
33 33 try:
34 34 self.matches = self.complete_request(text)
35 35 except Empty:
36 36 print('WARNING: Kernel timeout on tab completion.')
37 37
38 38 try:
39 39 return self.matches[state]
40 40 except IndexError:
41 41 return None
42 42
43 43 def complete(self, text, line, cursor_pos=None):
44 44 return self.rlcomplete(text, 0)
@@ -1,466 +1,465
1 1 # -*- coding: utf-8 -*-
2 """Frontend of ipython working with python-zmq
2 """terminal client to the IPython kernel
3 3
4 Ipython's frontend, is a ipython interface that send request to kernel and proccess the kernel's outputs.
5
6 For more details, see the ipython-zmq design
7 4 """
8 5 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2013 The IPython Development Team
10 7 #
11 8 # Distributed under the terms of the BSD License. The full license is in
12 9 # the file COPYING, distributed as part of this software.
13 10 #-----------------------------------------------------------------------------
14 11
15 12 #-----------------------------------------------------------------------------
16 13 # Imports
17 14 #-----------------------------------------------------------------------------
18 15 from __future__ import print_function
19 16
20 17 import bdb
21 18 import signal
22 19 import os
23 20 import sys
24 21 import time
25 22 import subprocess
26 23 from io import BytesIO
27 24 import base64
28 25
29 26 from Queue import Empty
30 27
31 28 try:
32 29 from contextlib import nested
33 30 except:
34 31 from IPython.utils.nested_context import nested
35 32
36 33 from IPython.core.alias import AliasManager, AliasError
37 34 from IPython.core import page
38 35 from IPython.utils.warn import warn, error, fatal
39 36 from IPython.utils import io
40 from IPython.utils.traitlets import List, Enum, Any
37 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode
41 38 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
42 39
43 40 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
44 41 from IPython.frontend.terminal.console.completer import ZMQCompleter
45 42
46 43
47 44 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
48 45 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
49 46 _executing = False
50 47
51 48 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
52 49 config=True, help=
53 50 """
54 51 Handler for image type output. This is useful, for example,
55 52 when connecting to the kernel in which pylab inline backend is
56 53 activated. There are four handlers defined. 'PIL': Use
57 54 Python Imaging Library to popup image; 'stream': Use an
58 55 external program to show the image. Image will be fed into
59 56 the STDIN of the program. You will need to configure
60 57 `stream_image_handler`; 'tempfile': Use an external program to
61 58 show the image. Image will be saved in a temporally file and
62 59 the program is called with the temporally file. You will need
63 60 to configure `tempfile_image_handler`; 'callable': You can set
64 61 any Python callable which is called with the image data. You
65 62 will need to configure `callable_image_handler`.
66 63 """
67 64 )
68 65
69 66 stream_image_handler = List(config=True, help=
70 67 """
71 68 Command to invoke an image viewer program when you are using
72 69 'stream' image handler. This option is a list of string where
73 70 the first element is the command itself and reminders are the
74 71 options for the command. Raw image data is given as STDIN to
75 72 the program.
76 73 """
77 74 )
78 75
79 76 tempfile_image_handler = List(config=True, help=
80 77 """
81 78 Command to invoke an image viewer program when you are using
82 79 'tempfile' image handler. This option is a list of string
83 80 where the first element is the command itself and reminders
84 81 are the options for the command. You can use {file} and
85 82 {format} in the string to represent the location of the
86 83 generated image file and image format.
87 84 """
88 85 )
89 86
90 87 callable_image_handler = Any(config=True, help=
91 88 """
92 89 Callable object called via 'callable' image handler with one
93 90 argument, `data`, which is `msg["content"]["data"]` where
94 91 `msg` is the message from iopub channel. For exmaple, you can
95 92 find base64 encoded PNG data as `data['image/png']`.
96 93 """
97 94 )
98 95
99 96 mime_preference = List(
100 97 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
101 98 config=True, allow_none=False, help=
102 99 """
103 100 Preferred object representation MIME type in order. First
104 101 matched MIME type will be used.
105 102 """
106 103 )
107 104
108 def __init__(self, *args, **kwargs):
109 self.km = kwargs.pop('kernel_manager')
110 self.session_id = self.km.session.session
111 super(ZMQTerminalInteractiveShell, self).__init__(*args, **kwargs)
105 manager = Instance('IPython.kernel.KernelManager')
106 client = Instance('IPython.kernel.KernelClient')
107 def _client_changed(self, name, old, new):
108 self.session_id = new.session.session
109 session_id = Unicode()
112 110
113 111 def init_completer(self):
114 112 """Initialize the completion machinery.
115 113
116 114 This creates completion machinery that can be used by client code,
117 115 either interactively in-process (typically triggered by the readline
118 116 library), programatically (such as in test suites) or out-of-prcess
119 117 (typically over the network by remote frontends).
120 118 """
121 119 from IPython.core.completerlib import (module_completer,
122 120 magic_run_completer, cd_completer)
123 121
124 self.Completer = ZMQCompleter(self, self.km)
122 self.Completer = ZMQCompleter(self, self.client)
125 123
126 124
127 125 self.set_hook('complete_command', module_completer, str_key = 'import')
128 126 self.set_hook('complete_command', module_completer, str_key = 'from')
129 127 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
130 128 self.set_hook('complete_command', cd_completer, str_key = '%cd')
131 129
132 130 # Only configure readline if we truly are using readline. IPython can
133 131 # do tab-completion over the network, in GUIs, etc, where readline
134 132 # itself may be absent
135 133 if self.has_readline:
136 134 self.set_readline_completer()
137 135
138 136 def run_cell(self, cell, store_history=True):
139 137 """Run a complete IPython cell.
140 138
141 139 Parameters
142 140 ----------
143 141 cell : str
144 142 The code (including IPython code such as %magic functions) to run.
145 143 store_history : bool
146 144 If True, the raw and translated cell will be stored in IPython's
147 145 history. For user code calling back into IPython's machinery, this
148 146 should be set to False.
149 147 """
150 148 if (not cell) or cell.isspace():
151 149 return
152 150
153 151 if cell.strip() == 'exit':
154 152 # explicitly handle 'exit' command
155 153 return self.ask_exit()
156 154
157 155 self._executing = True
158 156 # flush stale replies, which could have been ignored, due to missed heartbeats
159 while self.km.shell_channel.msg_ready():
160 self.km.shell_channel.get_msg()
157 while self.client.shell_channel.msg_ready():
158 self.client.shell_channel.get_msg()
161 159 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
162 msg_id = self.km.shell_channel.execute(cell, not store_history)
163 while not self.km.shell_channel.msg_ready() and self.km.is_alive:
160 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 while not self.client.shell_channel.msg_ready() and self.client.is_alive():
164 162 try:
165 163 self.handle_stdin_request(timeout=0.05)
166 164 except Empty:
167 165 # display intermediate print statements, etc.
168 166 self.handle_iopub()
169 167 pass
170 if self.km.shell_channel.msg_ready():
168 if self.client.shell_channel.msg_ready():
171 169 self.handle_execute_reply(msg_id)
172 170 self._executing = False
173 171
174 172 #-----------------
175 173 # message handlers
176 174 #-----------------
177 175
178 176 def handle_execute_reply(self, msg_id):
179 msg = self.km.shell_channel.get_msg()
177 msg = self.client.shell_channel.get_msg()
180 178 if msg["parent_header"].get("msg_id", None) == msg_id:
181 179
182 180 self.handle_iopub()
183 181
184 182 content = msg["content"]
185 183 status = content['status']
186 184
187 185 if status == 'aborted':
188 186 self.write('Aborted\n')
189 187 return
190 188 elif status == 'ok':
191 189 # print execution payloads as well:
192 190 for item in content["payload"]:
193 191 text = item.get('text', None)
194 192 if text:
195 193 page.page(text)
196 194
197 195 elif status == 'error':
198 196 for frame in content["traceback"]:
199 197 print(frame, file=io.stderr)
200 198
201 199 self.execution_count = int(content["execution_count"] + 1)
202 200
203 201
204 202 def handle_iopub(self):
205 203 """ Method to procces subscribe channel's messages
206 204
207 205 This method reads a message and processes the content in different
208 206 outputs like stdout, stderr, pyout and status
209 207
210 208 Arguments:
211 209 sub_msg: message receive from kernel in the sub socket channel
212 210 capture by kernel manager.
213 211 """
214 while self.km.iopub_channel.msg_ready():
215 sub_msg = self.km.iopub_channel.get_msg()
212 while self.client.iopub_channel.msg_ready():
213 sub_msg = self.client.iopub_channel.get_msg()
216 214 msg_type = sub_msg['header']['msg_type']
217 215 parent = sub_msg["parent_header"]
218 216 if (not parent) or self.session_id == parent['session']:
219 217 if msg_type == 'status' :
220 218 if sub_msg["content"]["execution_state"] == "busy" :
221 219 pass
222 220
223 221 elif msg_type == 'stream' :
224 222 if sub_msg["content"]["name"] == "stdout":
225 223 print(sub_msg["content"]["data"], file=io.stdout, end="")
226 224 io.stdout.flush()
227 225 elif sub_msg["content"]["name"] == "stderr" :
228 226 print(sub_msg["content"]["data"], file=io.stderr, end="")
229 227 io.stderr.flush()
230 228
231 229 elif msg_type == 'pyout':
232 230 self.execution_count = int(sub_msg["content"]["execution_count"])
233 231 format_dict = sub_msg["content"]["data"]
234 232 self.handle_rich_data(format_dict)
235 233 # taken from DisplayHook.__call__:
236 234 hook = self.displayhook
237 235 hook.start_displayhook()
238 236 hook.write_output_prompt()
239 237 hook.write_format_data(format_dict)
240 238 hook.log_output(format_dict)
241 239 hook.finish_displayhook()
242 240
243 241 elif msg_type == 'display_data':
244 242 self.handle_rich_data(sub_msg["content"]["data"])
245 243
246 244 _imagemime = {
247 245 'image/png': 'png',
248 246 'image/jpeg': 'jpeg',
249 247 'image/svg+xml': 'svg',
250 248 }
251 249
252 250 def handle_rich_data(self, data):
253 251 for mime in self.mime_preference:
254 252 if mime in data and mime in self._imagemime:
255 253 self.handle_image(data, mime)
256 254 return
257 255
258 256 def handle_image(self, data, mime):
259 257 handler = getattr(
260 258 self, 'handle_image_{0}'.format(self.image_handler), None)
261 259 if handler:
262 260 handler(data, mime)
263 261
264 262 def handle_image_PIL(self, data, mime):
265 263 if mime not in ('image/png', 'image/jpeg'):
266 264 return
267 265 import PIL.Image
268 266 raw = base64.decodestring(data[mime].encode('ascii'))
269 267 img = PIL.Image.open(BytesIO(raw))
270 268 img.show()
271 269
272 270 def handle_image_stream(self, data, mime):
273 271 raw = base64.decodestring(data[mime].encode('ascii'))
274 272 imageformat = self._imagemime[mime]
275 273 fmt = dict(format=imageformat)
276 274 args = [s.format(**fmt) for s in self.stream_image_handler]
277 275 with open(os.devnull, 'w') as devnull:
278 276 proc = subprocess.Popen(
279 277 args, stdin=subprocess.PIPE,
280 278 stdout=devnull, stderr=devnull)
281 279 proc.communicate(raw)
282 280
283 281 def handle_image_tempfile(self, data, mime):
284 282 raw = base64.decodestring(data[mime].encode('ascii'))
285 283 imageformat = self._imagemime[mime]
286 284 filename = 'tmp.{0}'.format(imageformat)
287 285 with nested(NamedFileInTemporaryDirectory(filename),
288 286 open(os.devnull, 'w')) as (f, devnull):
289 287 f.write(raw)
290 288 f.flush()
291 289 fmt = dict(file=f.name, format=imageformat)
292 290 args = [s.format(**fmt) for s in self.tempfile_image_handler]
293 291 subprocess.call(args, stdout=devnull, stderr=devnull)
294 292
295 293 def handle_image_callable(self, data, mime):
296 294 self.callable_image_handler(data)
297 295
298 296 def handle_stdin_request(self, timeout=0.1):
299 297 """ Method to capture raw_input
300 298 """
301 msg_rep = self.km.stdin_channel.get_msg(timeout=timeout)
299 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
302 300 # in case any iopub came while we were waiting:
303 301 self.handle_iopub()
304 302 if self.session_id == msg_rep["parent_header"].get("session"):
305 303 # wrap SIGINT handler
306 304 real_handler = signal.getsignal(signal.SIGINT)
307 305 def double_int(sig,frame):
308 306 # call real handler (forwards sigint to kernel),
309 307 # then raise local interrupt, stopping local raw_input
310 308 real_handler(sig,frame)
311 309 raise KeyboardInterrupt
312 310 signal.signal(signal.SIGINT, double_int)
313 311
314 312 try:
315 313 raw_data = raw_input(msg_rep["content"]["prompt"])
316 314 except EOFError:
317 315 # turn EOFError into EOF character
318 316 raw_data = '\x04'
319 317 except KeyboardInterrupt:
320 318 sys.stdout.write('\n')
321 319 return
322 320 finally:
323 321 # restore SIGINT handler
324 322 signal.signal(signal.SIGINT, real_handler)
325 323
326 324 # only send stdin reply if there *was not* another request
327 325 # or execution finished while we were reading.
328 if not (self.km.stdin_channel.msg_ready() or self.km.shell_channel.msg_ready()):
329 self.km.stdin_channel.input(raw_data)
326 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
327 self.client.stdin_channel.input(raw_data)
330 328
331 329 def mainloop(self, display_banner=False):
332 330 while True:
333 331 try:
334 332 self.interact(display_banner=display_banner)
335 333 #self.interact_with_readline()
336 334 # XXX for testing of a readline-decoupled repl loop, call
337 335 # interact_with_readline above
338 336 break
339 337 except KeyboardInterrupt:
340 338 # this should not be necessary, but KeyboardInterrupt
341 339 # handling seems rather unpredictable...
342 340 self.write("\nKeyboardInterrupt in interact()\n")
343 341
344 342 def wait_for_kernel(self, timeout=None):
345 343 """method to wait for a kernel to be ready"""
346 344 tic = time.time()
347 self.km.hb_channel.unpause()
345 self.client.hb_channel.unpause()
348 346 while True:
349 347 self.run_cell('1', False)
350 if self.km.hb_channel.is_beating():
348 if self.client.hb_channel.is_beating():
351 349 # heart failure was not the reason this returned
352 350 break
353 351 else:
354 352 # heart failed
355 353 if timeout is not None and (time.time() - tic) > timeout:
356 354 return False
357 355 return True
358 356
359 357 def interact(self, display_banner=None):
360 358 """Closely emulate the interactive Python console."""
361 359
362 360 # batch run -> do not interact
363 361 if self.exit_now:
364 362 return
365 363
366 364 if display_banner is None:
367 365 display_banner = self.display_banner
368 366
369 367 if isinstance(display_banner, basestring):
370 368 self.show_banner(display_banner)
371 369 elif display_banner:
372 370 self.show_banner()
373 371
374 372 more = False
375 373
376 374 # run a non-empty no-op, so that we don't get a prompt until
377 375 # we know the kernel is ready. This keeps the connection
378 376 # message above the first prompt.
379 377 if not self.wait_for_kernel(3):
380 378 error("Kernel did not respond\n")
381 379 return
382 380
383 381 if self.has_readline:
384 382 self.readline_startup_hook(self.pre_readline)
385 383 hlen_b4_cell = self.readline.get_current_history_length()
386 384 else:
387 385 hlen_b4_cell = 0
388 386 # exit_now is set by a call to %Exit or %Quit, through the
389 387 # ask_exit callback.
390 388
391 389 while not self.exit_now:
392 if not self.km.is_alive:
390 if not self.client.is_alive():
393 391 # kernel died, prompt for action or exit
394 action = "restart" if self.km.has_kernel else "wait for restart"
392
393 action = "restart" if self.manager else "wait for restart"
395 394 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
396 395 if ans:
397 if self.km.has_kernel:
398 self.km.restart_kernel(True)
396 if self.manager:
397 self.manager.restart_kernel(True)
399 398 self.wait_for_kernel(3)
400 399 else:
401 400 self.exit_now = True
402 401 continue
403 402 try:
404 403 # protect prompt block from KeyboardInterrupt
405 404 # when sitting on ctrl-C
406 405 self.hooks.pre_prompt_hook()
407 406 if more:
408 407 try:
409 408 prompt = self.prompt_manager.render('in2')
410 409 except Exception:
411 410 self.showtraceback()
412 411 if self.autoindent:
413 412 self.rl_do_indent = True
414 413
415 414 else:
416 415 try:
417 416 prompt = self.separate_in + self.prompt_manager.render('in')
418 417 except Exception:
419 418 self.showtraceback()
420 419
421 420 line = self.raw_input(prompt)
422 421 if self.exit_now:
423 422 # quick exit on sys.std[in|out] close
424 423 break
425 424 if self.autoindent:
426 425 self.rl_do_indent = False
427 426
428 427 except KeyboardInterrupt:
429 428 #double-guard against keyboardinterrupts during kbdint handling
430 429 try:
431 430 self.write('\nKeyboardInterrupt\n')
432 431 source_raw = self.input_splitter.source_raw_reset()[1]
433 432 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
434 433 more = False
435 434 except KeyboardInterrupt:
436 435 pass
437 436 except EOFError:
438 437 if self.autoindent:
439 438 self.rl_do_indent = False
440 439 if self.has_readline:
441 440 self.readline_startup_hook(None)
442 441 self.write('\n')
443 442 self.exit()
444 443 except bdb.BdbQuit:
445 444 warn('The Python debugger has exited with a BdbQuit exception.\n'
446 445 'Because of how pdb handles the stack, it is impossible\n'
447 446 'for IPython to properly format this particular exception.\n'
448 447 'IPython will resume normal operation.')
449 448 except:
450 449 # exceptions here are VERY RARE, but they can be triggered
451 450 # asynchronously by signal handlers, for example.
452 451 self.showtraceback()
453 452 else:
454 453 self.input_splitter.push(line)
455 454 more = self.input_splitter.push_accepts_more()
456 455 if (self.SyntaxTB.last_syntax_error and
457 456 self.autoedit_syntax):
458 457 self.edit_syntax_error()
459 458 if not more:
460 459 source_raw = self.input_splitter.source_raw_reset()[1]
461 460 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
462 461 self.run_cell(source_raw)
463 462
464 463
465 464 # Turn off the exit flag, so the mainloop can be restarted if desired
466 465 self.exit_now = False
@@ -1,95 +1,95
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2012 The IPython Development Team
3 3 #
4 4 # Distributed under the terms of the BSD License. The full license is in
5 5 # the file COPYING, distributed as part of this software.
6 6 #-----------------------------------------------------------------------------
7 7
8 8 import os
9 9 import sys
10 10 import unittest
11 11 import base64
12 12
13 from IPython.kernel.kernelmanager import KernelManager
13 from IPython.kernel import KernelClient
14 14 from IPython.frontend.terminal.console.interactiveshell \
15 15 import ZMQTerminalInteractiveShell
16 16 from IPython.utils.tempdir import TemporaryDirectory
17 17 from IPython.testing.tools import monkeypatch
18 18 from IPython.testing.decorators import skip_without
19 19 from IPython.utils.ipstruct import Struct
20 20
21 21
22 22 SCRIPT_PATH = os.path.join(
23 23 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
24 24
25 25
26 26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
27 27
28 28 def setUp(self):
29 km = KernelManager()
30 self.shell = ZMQTerminalInteractiveShell(kernel_manager=km)
29 client = KernelClient()
30 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
31 31 self.raw = b'dummy data'
32 32 self.mime = 'image/png'
33 33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
34 34
35 35 def test_no_call_by_default(self):
36 36 def raise_if_called(*args, **kwds):
37 37 assert False
38 38
39 39 shell = self.shell
40 40 shell.handle_image_PIL = raise_if_called
41 41 shell.handle_image_stream = raise_if_called
42 42 shell.handle_image_tempfile = raise_if_called
43 43 shell.handle_image_callable = raise_if_called
44 44
45 45 shell.handle_image(None, None) # arguments are dummy
46 46
47 47 @skip_without('PIL')
48 48 def test_handle_image_PIL(self):
49 49 import PIL.Image
50 50
51 51 open_called_with = []
52 52 show_called_with = []
53 53
54 54 def fake_open(arg):
55 55 open_called_with.append(arg)
56 56 return Struct(show=lambda: show_called_with.append(None))
57 57
58 58 with monkeypatch(PIL.Image, 'open', fake_open):
59 59 self.shell.handle_image_PIL(self.data, self.mime)
60 60
61 61 self.assertEqual(len(open_called_with), 1)
62 62 self.assertEqual(len(show_called_with), 1)
63 63 self.assertEqual(open_called_with[0].getvalue(), self.raw)
64 64
65 65 def check_handler_with_file(self, inpath, handler):
66 66 shell = self.shell
67 67 configname = '{0}_image_handler'.format(handler)
68 68 funcname = 'handle_image_{0}'.format(handler)
69 69
70 70 assert hasattr(shell, configname)
71 71 assert hasattr(shell, funcname)
72 72
73 73 with TemporaryDirectory() as tmpdir:
74 74 outpath = os.path.join(tmpdir, 'data')
75 75 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
76 76 setattr(shell, configname, cmd)
77 77 getattr(shell, funcname)(self.data, self.mime)
78 78 # cmd is called and file is closed. So it's safe to open now.
79 79 with open(outpath, 'rb') as file:
80 80 transferred = file.read()
81 81
82 82 self.assertEqual(transferred, self.raw)
83 83
84 84 def test_handle_image_stream(self):
85 85 self.check_handler_with_file('-', 'stream')
86 86
87 87 def test_handle_image_tempfile(self):
88 88 self.check_handler_with_file('{file}', 'tempfile')
89 89
90 90 def test_handle_image_callable(self):
91 91 called_with = []
92 92 self.shell.callable_image_handler = called_with.append
93 93 self.shell.handle_image_callable(self.data, self.mime)
94 94 self.assertEqual(len(called_with), 1)
95 95 assert called_with[0] is self.data
@@ -1,752 +1,754
1 1 # -*- coding: utf-8 -*-
2 2 """Subclass of InteractiveShell for terminal based frontends."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 import bdb
19 19 import os
20 20 import re
21 21 import sys
22 22 import textwrap
23 23
24 24 # We need to use nested to support python 2.6, once we move to >=2.7, we can
25 25 # use the with keyword's new builtin support for nested managers
26 26 try:
27 27 from contextlib import nested
28 28 except:
29 29 from IPython.utils.nested_context import nested
30 30
31 31 from IPython.core.error import TryNext, UsageError
32 32 from IPython.core.usage import interactive_usage, default_banner
33 33 from IPython.core.inputsplitter import IPythonInputSplitter
34 34 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
35 35 from IPython.core.magic import Magics, magics_class, line_magic
36 36 from IPython.testing.skipdoctest import skip_doctest
37 37 from IPython.utils.encoding import get_stream_enc
38 38 from IPython.utils import py3compat
39 39 from IPython.utils.terminal import toggle_set_term_title, set_term_title
40 40 from IPython.utils.process import abbrev_cwd
41 41 from IPython.utils.warn import warn, error
42 42 from IPython.utils.text import num_ini_spaces, SList, strip_email_quotes
43 43 from IPython.utils.traitlets import Integer, CBool, Unicode
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Utilities
47 47 #-----------------------------------------------------------------------------
48 48
49 49 def get_default_editor():
50 50 try:
51 51 ed = os.environ['EDITOR']
52 52 except KeyError:
53 53 if os.name == 'posix':
54 54 ed = 'vi' # the only one guaranteed to be there!
55 55 else:
56 56 ed = 'notepad' # same in Windows!
57 57 return ed
58 58
59 59
60 60 def get_pasted_lines(sentinel, l_input=py3compat.input):
61 61 """ Yield pasted lines until the user enters the given sentinel value.
62 62 """
63 63 print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \
64 64 % sentinel)
65 65 while True:
66 66 try:
67 67 l = l_input(':')
68 68 if l == sentinel:
69 69 return
70 70 else:
71 71 yield l
72 72 except EOFError:
73 73 print('<EOF>')
74 74 return
75 75
76 76
77 77 #------------------------------------------------------------------------
78 78 # Terminal-specific magics
79 79 #------------------------------------------------------------------------
80 80
81 81 @magics_class
82 82 class TerminalMagics(Magics):
83 83 def __init__(self, shell):
84 84 super(TerminalMagics, self).__init__(shell)
85 85 self.input_splitter = IPythonInputSplitter()
86 86
87 87 def cleanup_input(self, block):
88 88 """Apply all possible IPython cleanups to an input block.
89 89
90 90 This means:
91 91
92 92 - remove any global leading whitespace (dedent)
93 93 - remove any email quotes ('>') if they are present in *all* lines
94 94 - apply all static inputsplitter transforms and break into sub-blocks
95 95 - apply prefilter() to each sub-block that is a single line.
96 96
97 97 Parameters
98 98 ----------
99 99 block : str
100 100 A possibly multiline input string of code.
101 101
102 102 Returns
103 103 -------
104 104 transformed block : str
105 105 The input, with all transformations above applied.
106 106 """
107 107 # We have to effectively implement client-side the loop that is done by
108 108 # the terminal frontend, and furthermore do it on a block that can
109 109 # possibly contain multiple statments pasted in one go.
110 110
111 111 # First, run the input through the block splitting code. We should
112 112 # eventually make this a self-contained method in the inputsplitter.
113 113 isp = self.input_splitter
114 114 isp.reset()
115 115 b = textwrap.dedent(block)
116 116
117 117 # Remove email quotes first. These must be consistently applied to
118 118 # *all* lines to be removed
119 119 b = strip_email_quotes(b)
120 120
121 121 # Split the input into independent sub-blocks so we can later do
122 122 # prefiltering (which must be done *only* to single-line inputs)
123 123 blocks = []
124 124 last_block = []
125 125 for line in b.splitlines():
126 126 isp.push(line)
127 127 last_block.append(line)
128 128 if not isp.push_accepts_more():
129 129 blocks.append(isp.source_reset())
130 130 last_block = []
131 131 if last_block:
132 132 blocks.append('\n'.join(last_block))
133 133
134 134 # Now, apply prefiltering to any one-line block to match the behavior
135 135 # of the interactive terminal
136 136 final_blocks = []
137 137 for block in blocks:
138 138 lines = block.splitlines()
139 139 if len(lines) == 1:
140 140 final_blocks.append(self.shell.prefilter(lines[0]))
141 141 else:
142 142 final_blocks.append(block)
143 143
144 144 # We now have the final version of the input code as a list of blocks,
145 145 # with all inputsplitter transformations applied and single-line blocks
146 146 # run through prefilter. For further processing, turn into a single
147 147 # string as the rest of our apis use string inputs.
148 148 return '\n'.join(final_blocks)
149 149
150 150 def store_or_execute(self, block, name):
151 151 """ Execute a block, or store it in a variable, per the user's request.
152 152 """
153 153
154 154 b = self.cleanup_input(block)
155 155 if name:
156 156 # If storing it for further editing
157 157 self.shell.user_ns[name] = SList(b.splitlines())
158 158 print("Block assigned to '%s'" % name)
159 159 else:
160 160 self.shell.user_ns['pasted_block'] = b
161 161 self.shell.using_paste_magics = True
162 162 try:
163 163 self.shell.run_cell(b)
164 164 finally:
165 165 self.shell.using_paste_magics = False
166 166
167 167 def rerun_pasted(self, name='pasted_block'):
168 168 """ Rerun a previously pasted command.
169 169 """
170 170 b = self.shell.user_ns.get(name)
171 171
172 172 # Sanity checks
173 173 if b is None:
174 174 raise UsageError('No previous pasted block available')
175 175 if not isinstance(b, basestring):
176 176 raise UsageError(
177 177 "Variable 'pasted_block' is not a string, can't execute")
178 178
179 179 print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b)))
180 180 self.shell.run_cell(b)
181 181
182 182 @line_magic
183 183 def autoindent(self, parameter_s = ''):
184 184 """Toggle autoindent on/off (if available)."""
185 185
186 186 self.shell.set_autoindent()
187 187 print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent])
188 188
189 189 @skip_doctest
190 190 @line_magic
191 191 def cpaste(self, parameter_s=''):
192 192 """Paste & execute a pre-formatted code block from clipboard.
193 193
194 194 You must terminate the block with '--' (two minus-signs) or Ctrl-D
195 195 alone on the line. You can also provide your own sentinel with '%paste
196 196 -s %%' ('%%' is the new sentinel for this operation)
197 197
198 198 The block is dedented prior to execution to enable execution of method
199 199 definitions. '>' and '+' characters at the beginning of a line are
200 200 ignored, to allow pasting directly from e-mails, diff files and
201 201 doctests (the '...' continuation prompt is also stripped). The
202 202 executed block is also assigned to variable named 'pasted_block' for
203 203 later editing with '%edit pasted_block'.
204 204
205 205 You can also pass a variable name as an argument, e.g. '%cpaste foo'.
206 206 This assigns the pasted block to variable 'foo' as string, without
207 207 dedenting or executing it (preceding >>> and + is still stripped)
208 208
209 209 '%cpaste -r' re-executes the block previously entered by cpaste.
210 210
211 211 Do not be alarmed by garbled output on Windows (it's a readline bug).
212 212 Just press enter and type -- (and press enter again) and the block
213 213 will be what was just pasted.
214 214
215 215 IPython statements (magics, shell escapes) are not supported (yet).
216 216
217 217 See also
218 218 --------
219 219 paste: automatically pull code from clipboard.
220 220
221 221 Examples
222 222 --------
223 223 ::
224 224
225 225 In [8]: %cpaste
226 226 Pasting code; enter '--' alone on the line to stop.
227 227 :>>> a = ["world!", "Hello"]
228 228 :>>> print " ".join(sorted(a))
229 229 :--
230 230 Hello world!
231 231 """
232 232 opts, name = self.parse_options(parameter_s, 'rs:', mode='string')
233 233 if 'r' in opts:
234 234 self.rerun_pasted()
235 235 return
236 236
237 237 sentinel = opts.get('s', '--')
238 238 block = '\n'.join(get_pasted_lines(sentinel))
239 239 self.store_or_execute(block, name)
240 240
241 241 @line_magic
242 242 def paste(self, parameter_s=''):
243 243 """Paste & execute a pre-formatted code block from clipboard.
244 244
245 245 The text is pulled directly from the clipboard without user
246 246 intervention and printed back on the screen before execution (unless
247 247 the -q flag is given to force quiet mode).
248 248
249 249 The block is dedented prior to execution to enable execution of method
250 250 definitions. '>' and '+' characters at the beginning of a line are
251 251 ignored, to allow pasting directly from e-mails, diff files and
252 252 doctests (the '...' continuation prompt is also stripped). The
253 253 executed block is also assigned to variable named 'pasted_block' for
254 254 later editing with '%edit pasted_block'.
255 255
256 256 You can also pass a variable name as an argument, e.g. '%paste foo'.
257 257 This assigns the pasted block to variable 'foo' as string, without
258 258 executing it (preceding >>> and + is still stripped).
259 259
260 260 Options
261 261 -------
262 262
263 263 -r: re-executes the block previously entered by cpaste.
264 264
265 265 -q: quiet mode: do not echo the pasted text back to the terminal.
266 266
267 267 IPython statements (magics, shell escapes) are not supported (yet).
268 268
269 269 See also
270 270 --------
271 271 cpaste: manually paste code into terminal until you mark its end.
272 272 """
273 273 opts, name = self.parse_options(parameter_s, 'rq', mode='string')
274 274 if 'r' in opts:
275 275 self.rerun_pasted()
276 276 return
277 277 try:
278 278 block = self.shell.hooks.clipboard_get()
279 279 except TryNext as clipboard_exc:
280 280 message = getattr(clipboard_exc, 'args')
281 281 if message:
282 282 error(message[0])
283 283 else:
284 284 error('Could not get text from the clipboard.')
285 285 return
286 286
287 287 # By default, echo back to terminal unless quiet mode is requested
288 288 if 'q' not in opts:
289 289 write = self.shell.write
290 290 write(self.shell.pycolorize(block))
291 291 if not block.endswith('\n'):
292 292 write('\n')
293 293 write("## -- End pasted text --\n")
294 294
295 295 self.store_or_execute(block, name)
296 296
297 297 # Class-level: add a '%cls' magic only on Windows
298 298 if sys.platform == 'win32':
299 299 @line_magic
300 300 def cls(self, s):
301 301 """Clear screen.
302 302 """
303 303 os.system("cls")
304 304
305 305 #-----------------------------------------------------------------------------
306 306 # Main class
307 307 #-----------------------------------------------------------------------------
308 308
309 309 class TerminalInteractiveShell(InteractiveShell):
310 310
311 311 autoedit_syntax = CBool(False, config=True,
312 312 help="auto editing of files with syntax errors.")
313 313 banner = Unicode('')
314 314 banner1 = Unicode(default_banner, config=True,
315 315 help="""The part of the banner to be printed before the profile"""
316 316 )
317 317 banner2 = Unicode('', config=True,
318 318 help="""The part of the banner to be printed after the profile"""
319 319 )
320 320 confirm_exit = CBool(True, config=True,
321 321 help="""
322 322 Set to confirm when you try to exit IPython with an EOF (Control-D
323 323 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
324 324 you can force a direct exit without any confirmation.""",
325 325 )
326 326 # This display_banner only controls whether or not self.show_banner()
327 327 # is called when mainloop/interact are called. The default is False
328 328 # because for the terminal based application, the banner behavior
329 329 # is controlled by Global.display_banner, which IPythonApp looks at
330 330 # to determine if *it* should call show_banner() by hand or not.
331 331 display_banner = CBool(False) # This isn't configurable!
332 332 embedded = CBool(False)
333 333 embedded_active = CBool(False)
334 334 editor = Unicode(get_default_editor(), config=True,
335 335 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
336 336 )
337 337 pager = Unicode('less', config=True,
338 338 help="The shell program to be used for paging.")
339 339
340 340 screen_length = Integer(0, config=True,
341 341 help=
342 342 """Number of lines of your screen, used to control printing of very
343 343 long strings. Strings longer than this number of lines will be sent
344 344 through a pager instead of directly printed. The default value for
345 345 this is 0, which means IPython will auto-detect your screen size every
346 346 time it needs to print certain potentially long strings (this doesn't
347 347 change the behavior of the 'print' keyword, it's only triggered
348 348 internally). If for some reason this isn't working well (it needs
349 349 curses support), specify it yourself. Otherwise don't change the
350 350 default.""",
351 351 )
352 352 term_title = CBool(False, config=True,
353 353 help="Enable auto setting the terminal title."
354 354 )
355 355
356 356 # This `using_paste_magics` is used to detect whether the code is being
357 357 # executed via paste magics functions
358 358 using_paste_magics = CBool(False)
359 359
360 360 # In the terminal, GUI control is done via PyOS_InputHook
361 361 @staticmethod
362 362 def enable_gui(gui=None, app=None):
363 363 """Switch amongst GUI input hooks by name.
364 364 """
365 365 # Deferred import
366 366 from IPython.lib.inputhook import enable_gui as real_enable_gui
367 367 return real_enable_gui(gui, app)
368 368
369 369 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
370 370 user_ns=None, user_module=None, custom_exceptions=((),None),
371 usage=None, banner1=None, banner2=None, display_banner=None):
371 usage=None, banner1=None, banner2=None, display_banner=None,
372 **kwargs):
372 373
373 374 super(TerminalInteractiveShell, self).__init__(
374 375 config=config, ipython_dir=ipython_dir, profile_dir=profile_dir, user_ns=user_ns,
375 user_module=user_module, custom_exceptions=custom_exceptions
376 user_module=user_module, custom_exceptions=custom_exceptions,
377 **kwargs
376 378 )
377 379 # use os.system instead of utils.process.system by default,
378 380 # because piped system doesn't make sense in the Terminal:
379 381 self.system = self.system_raw
380 382
381 383 self.init_term_title()
382 384 self.init_usage(usage)
383 385 self.init_banner(banner1, banner2, display_banner)
384 386
385 387 #-------------------------------------------------------------------------
386 388 # Overrides of init stages
387 389 #-------------------------------------------------------------------------
388 390
389 391 def init_display_formatter(self):
390 392 super(TerminalInteractiveShell, self).init_display_formatter()
391 393 # terminal only supports plaintext
392 394 self.display_formatter.active_types = ['text/plain']
393 395
394 396 #-------------------------------------------------------------------------
395 397 # Things related to the terminal
396 398 #-------------------------------------------------------------------------
397 399
398 400 @property
399 401 def usable_screen_length(self):
400 402 if self.screen_length == 0:
401 403 return 0
402 404 else:
403 405 num_lines_bot = self.separate_in.count('\n')+1
404 406 return self.screen_length - num_lines_bot
405 407
406 408 def init_term_title(self):
407 409 # Enable or disable the terminal title.
408 410 if self.term_title:
409 411 toggle_set_term_title(True)
410 412 set_term_title('IPython: ' + abbrev_cwd())
411 413 else:
412 414 toggle_set_term_title(False)
413 415
414 416 #-------------------------------------------------------------------------
415 417 # Things related to aliases
416 418 #-------------------------------------------------------------------------
417 419
418 420 def init_alias(self):
419 421 # The parent class defines aliases that can be safely used with any
420 422 # frontend.
421 423 super(TerminalInteractiveShell, self).init_alias()
422 424
423 425 # Now define aliases that only make sense on the terminal, because they
424 426 # need direct access to the console in a way that we can't emulate in
425 427 # GUI or web frontend
426 428 if os.name == 'posix':
427 429 aliases = [('clear', 'clear'), ('more', 'more'), ('less', 'less'),
428 430 ('man', 'man')]
429 431 elif os.name == 'nt':
430 432 aliases = [('cls', 'cls')]
431 433
432 434
433 435 for name, cmd in aliases:
434 436 self.alias_manager.define_alias(name, cmd)
435 437
436 438 #-------------------------------------------------------------------------
437 439 # Things related to the banner and usage
438 440 #-------------------------------------------------------------------------
439 441
440 442 def _banner1_changed(self):
441 443 self.compute_banner()
442 444
443 445 def _banner2_changed(self):
444 446 self.compute_banner()
445 447
446 448 def _term_title_changed(self, name, new_value):
447 449 self.init_term_title()
448 450
449 451 def init_banner(self, banner1, banner2, display_banner):
450 452 if banner1 is not None:
451 453 self.banner1 = banner1
452 454 if banner2 is not None:
453 455 self.banner2 = banner2
454 456 if display_banner is not None:
455 457 self.display_banner = display_banner
456 458 self.compute_banner()
457 459
458 460 def show_banner(self, banner=None):
459 461 if banner is None:
460 462 banner = self.banner
461 463 self.write(banner)
462 464
463 465 def compute_banner(self):
464 466 self.banner = self.banner1
465 467 if self.profile and self.profile != 'default':
466 468 self.banner += '\nIPython profile: %s\n' % self.profile
467 469 if self.banner2:
468 470 self.banner += '\n' + self.banner2
469 471
470 472 def init_usage(self, usage=None):
471 473 if usage is None:
472 474 self.usage = interactive_usage
473 475 else:
474 476 self.usage = usage
475 477
476 478 #-------------------------------------------------------------------------
477 479 # Mainloop and code execution logic
478 480 #-------------------------------------------------------------------------
479 481
480 482 def mainloop(self, display_banner=None):
481 483 """Start the mainloop.
482 484
483 485 If an optional banner argument is given, it will override the
484 486 internally created default banner.
485 487 """
486 488
487 489 with nested(self.builtin_trap, self.display_trap):
488 490
489 491 while 1:
490 492 try:
491 493 self.interact(display_banner=display_banner)
492 494 #self.interact_with_readline()
493 495 # XXX for testing of a readline-decoupled repl loop, call
494 496 # interact_with_readline above
495 497 break
496 498 except KeyboardInterrupt:
497 499 # this should not be necessary, but KeyboardInterrupt
498 500 # handling seems rather unpredictable...
499 501 self.write("\nKeyboardInterrupt in interact()\n")
500 502
501 503 def _replace_rlhist_multiline(self, source_raw, hlen_before_cell):
502 504 """Store multiple lines as a single entry in history"""
503 505
504 506 # do nothing without readline or disabled multiline
505 507 if not self.has_readline or not self.multiline_history:
506 508 return hlen_before_cell
507 509
508 510 # windows rl has no remove_history_item
509 511 if not hasattr(self.readline, "remove_history_item"):
510 512 return hlen_before_cell
511 513
512 514 # skip empty cells
513 515 if not source_raw.rstrip():
514 516 return hlen_before_cell
515 517
516 518 # nothing changed do nothing, e.g. when rl removes consecutive dups
517 519 hlen = self.readline.get_current_history_length()
518 520 if hlen == hlen_before_cell:
519 521 return hlen_before_cell
520 522
521 523 for i in range(hlen - hlen_before_cell):
522 524 self.readline.remove_history_item(hlen - i - 1)
523 525 stdin_encoding = get_stream_enc(sys.stdin, 'utf-8')
524 526 self.readline.add_history(py3compat.unicode_to_str(source_raw.rstrip(),
525 527 stdin_encoding))
526 528 return self.readline.get_current_history_length()
527 529
528 530 def interact(self, display_banner=None):
529 531 """Closely emulate the interactive Python console."""
530 532
531 533 # batch run -> do not interact
532 534 if self.exit_now:
533 535 return
534 536
535 537 if display_banner is None:
536 538 display_banner = self.display_banner
537 539
538 540 if isinstance(display_banner, basestring):
539 541 self.show_banner(display_banner)
540 542 elif display_banner:
541 543 self.show_banner()
542 544
543 545 more = False
544 546
545 547 if self.has_readline:
546 548 self.readline_startup_hook(self.pre_readline)
547 549 hlen_b4_cell = self.readline.get_current_history_length()
548 550 else:
549 551 hlen_b4_cell = 0
550 552 # exit_now is set by a call to %Exit or %Quit, through the
551 553 # ask_exit callback.
552 554
553 555 while not self.exit_now:
554 556 self.hooks.pre_prompt_hook()
555 557 if more:
556 558 try:
557 559 prompt = self.prompt_manager.render('in2')
558 560 except:
559 561 self.showtraceback()
560 562 if self.autoindent:
561 563 self.rl_do_indent = True
562 564
563 565 else:
564 566 try:
565 567 prompt = self.separate_in + self.prompt_manager.render('in')
566 568 except:
567 569 self.showtraceback()
568 570 try:
569 571 line = self.raw_input(prompt)
570 572 if self.exit_now:
571 573 # quick exit on sys.std[in|out] close
572 574 break
573 575 if self.autoindent:
574 576 self.rl_do_indent = False
575 577
576 578 except KeyboardInterrupt:
577 579 #double-guard against keyboardinterrupts during kbdint handling
578 580 try:
579 581 self.write('\nKeyboardInterrupt\n')
580 582 source_raw = self.input_splitter.source_raw_reset()[1]
581 583 hlen_b4_cell = \
582 584 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
583 585 more = False
584 586 except KeyboardInterrupt:
585 587 pass
586 588 except EOFError:
587 589 if self.autoindent:
588 590 self.rl_do_indent = False
589 591 if self.has_readline:
590 592 self.readline_startup_hook(None)
591 593 self.write('\n')
592 594 self.exit()
593 595 except bdb.BdbQuit:
594 596 warn('The Python debugger has exited with a BdbQuit exception.\n'
595 597 'Because of how pdb handles the stack, it is impossible\n'
596 598 'for IPython to properly format this particular exception.\n'
597 599 'IPython will resume normal operation.')
598 600 except:
599 601 # exceptions here are VERY RARE, but they can be triggered
600 602 # asynchronously by signal handlers, for example.
601 603 self.showtraceback()
602 604 else:
603 605 self.input_splitter.push(line)
604 606 more = self.input_splitter.push_accepts_more()
605 607 if (self.SyntaxTB.last_syntax_error and
606 608 self.autoedit_syntax):
607 609 self.edit_syntax_error()
608 610 if not more:
609 611 source_raw = self.input_splitter.source_raw_reset()[1]
610 612 self.run_cell(source_raw, store_history=True)
611 613 hlen_b4_cell = \
612 614 self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
613 615
614 616 # Turn off the exit flag, so the mainloop can be restarted if desired
615 617 self.exit_now = False
616 618
617 619 def raw_input(self, prompt=''):
618 620 """Write a prompt and read a line.
619 621
620 622 The returned line does not include the trailing newline.
621 623 When the user enters the EOF key sequence, EOFError is raised.
622 624
623 625 Optional inputs:
624 626
625 627 - prompt(''): a string to be printed to prompt the user.
626 628
627 629 - continue_prompt(False): whether this line is the first one or a
628 630 continuation in a sequence of inputs.
629 631 """
630 632 # Code run by the user may have modified the readline completer state.
631 633 # We must ensure that our completer is back in place.
632 634
633 635 if self.has_readline:
634 636 self.set_readline_completer()
635 637
636 638 # raw_input expects str, but we pass it unicode sometimes
637 639 prompt = py3compat.cast_bytes_py2(prompt)
638 640
639 641 try:
640 642 line = py3compat.str_to_unicode(self.raw_input_original(prompt))
641 643 except ValueError:
642 644 warn("\n********\nYou or a %run:ed script called sys.stdin.close()"
643 645 " or sys.stdout.close()!\nExiting IPython!\n")
644 646 self.ask_exit()
645 647 return ""
646 648
647 649 # Try to be reasonably smart about not re-indenting pasted input more
648 650 # than necessary. We do this by trimming out the auto-indent initial
649 651 # spaces, if the user's actual input started itself with whitespace.
650 652 if self.autoindent:
651 653 if num_ini_spaces(line) > self.indent_current_nsp:
652 654 line = line[self.indent_current_nsp:]
653 655 self.indent_current_nsp = 0
654 656
655 657 return line
656 658
657 659 #-------------------------------------------------------------------------
658 660 # Methods to support auto-editing of SyntaxErrors.
659 661 #-------------------------------------------------------------------------
660 662
661 663 def edit_syntax_error(self):
662 664 """The bottom half of the syntax error handler called in the main loop.
663 665
664 666 Loop until syntax error is fixed or user cancels.
665 667 """
666 668
667 669 while self.SyntaxTB.last_syntax_error:
668 670 # copy and clear last_syntax_error
669 671 err = self.SyntaxTB.clear_err_state()
670 672 if not self._should_recompile(err):
671 673 return
672 674 try:
673 675 # may set last_syntax_error again if a SyntaxError is raised
674 676 self.safe_execfile(err.filename,self.user_ns)
675 677 except:
676 678 self.showtraceback()
677 679 else:
678 680 try:
679 681 f = open(err.filename)
680 682 try:
681 683 # This should be inside a display_trap block and I
682 684 # think it is.
683 685 sys.displayhook(f.read())
684 686 finally:
685 687 f.close()
686 688 except:
687 689 self.showtraceback()
688 690
689 691 def _should_recompile(self,e):
690 692 """Utility routine for edit_syntax_error"""
691 693
692 694 if e.filename in ('<ipython console>','<input>','<string>',
693 695 '<console>','<BackgroundJob compilation>',
694 696 None):
695 697
696 698 return False
697 699 try:
698 700 if (self.autoedit_syntax and
699 701 not self.ask_yes_no('Return to editor to correct syntax error? '
700 702 '[Y/n] ','y')):
701 703 return False
702 704 except EOFError:
703 705 return False
704 706
705 707 def int0(x):
706 708 try:
707 709 return int(x)
708 710 except TypeError:
709 711 return 0
710 712 # always pass integer line and offset values to editor hook
711 713 try:
712 714 self.hooks.fix_error_editor(e.filename,
713 715 int0(e.lineno),int0(e.offset),e.msg)
714 716 except TryNext:
715 717 warn('Could not open editor')
716 718 return False
717 719 return True
718 720
719 721 #-------------------------------------------------------------------------
720 722 # Things related to exiting
721 723 #-------------------------------------------------------------------------
722 724
723 725 def ask_exit(self):
724 726 """ Ask the shell to exit. Can be overiden and used as a callback. """
725 727 self.exit_now = True
726 728
727 729 def exit(self):
728 730 """Handle interactive exit.
729 731
730 732 This method calls the ask_exit callback."""
731 733 if self.confirm_exit:
732 734 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y'):
733 735 self.ask_exit()
734 736 else:
735 737 self.ask_exit()
736 738
737 739 #-------------------------------------------------------------------------
738 740 # Things related to magics
739 741 #-------------------------------------------------------------------------
740 742
741 743 def init_magics(self):
742 744 super(TerminalInteractiveShell, self).init_magics()
743 745 self.register_magics(TerminalMagics)
744 746
745 747 def showindentationerror(self):
746 748 super(TerminalInteractiveShell, self).showindentationerror()
747 749 if not self.using_paste_magics:
748 750 print("If you want to paste code into IPython, try the "
749 751 "%paste and %cpaste magic functions.")
750 752
751 753
752 754 InteractiveShellABC.register(TerminalInteractiveShell)
@@ -1,10 +1,11
1 1 """IPython kernels and associated utilities"""
2 2
3 3 # just for friendlier zmq version check
4 4 from . import zmq
5 5
6 6 from .connect import *
7 7 from .launcher import *
8 from .kernelmanager import KernelManager
9 from .blockingkernelmanager import BlockingKernelManager
8 from .client import KernelClient
9 from .manager import KernelManager
10 from .blocking import BlockingKernelClient
10 11 from .multikernelmanager import MultiKernelManager
@@ -1,90 +1,79
1 """ Implements a fully blocking kernel manager.
1 """Blocking channels
2 2
3 3 Useful for test suites and blocking terminal interfaces.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2012 The IPython Development Team
6 # Copyright (C) 2013 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING.txt, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 import Queue
17 17
18 from IPython.utils.traitlets import Type
19 from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \
18 from IPython.kernel.channels import IOPubChannel, HBChannel, \
20 19 ShellChannel, StdInChannel
21 20
22 21 #-----------------------------------------------------------------------------
23 22 # Blocking kernel manager
24 23 #-----------------------------------------------------------------------------
25 24
26 25
27 26 class BlockingChannelMixin(object):
28 27
29 28 def __init__(self, *args, **kwds):
30 29 super(BlockingChannelMixin, self).__init__(*args, **kwds)
31 30 self._in_queue = Queue.Queue()
32 31
33 32 def call_handlers(self, msg):
34 33 self._in_queue.put(msg)
35 34
36 35 def get_msg(self, block=True, timeout=None):
37 36 """ Gets a message if there is one that is ready. """
38 37 if timeout is None:
39 38 # Queue.get(timeout=None) has stupid uninteruptible
40 39 # behavior, so wait for a week instead
41 40 timeout = 604800
42 41 return self._in_queue.get(block, timeout)
43 42
44 43 def get_msgs(self):
45 44 """ Get all messages that are currently ready. """
46 45 msgs = []
47 46 while True:
48 47 try:
49 48 msgs.append(self.get_msg(block=False))
50 49 except Queue.Empty:
51 50 break
52 51 return msgs
53 52
54 53 def msg_ready(self):
55 54 """ Is there a message that has been received? """
56 55 return not self._in_queue.empty()
57 56
58 57
59 58 class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):
60 59 pass
61 60
62 61
63 62 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
64 63 pass
65 64
66 65
67 66 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
68 67 pass
69 68
70 69
71 70 class BlockingHBChannel(HBChannel):
72 71
73 72 # This kernel needs quicker monitoring, shorten to 1 sec.
74 73 # less than 0.5s is unreliable, and will get occasional
75 74 # false reports of missed beats.
76 75 time_to_dead = 1.
77 76
78 77 def call_handlers(self, since_last_heartbeat):
79 78 """ Pause beating on missed heartbeat. """
80 79 pass
81
82
83 class BlockingKernelManager(KernelManager):
84
85 # The classes to use for the various channels.
86 shell_channel_class = Type(BlockingShellChannel)
87 iopub_channel_class = Type(BlockingIOPubChannel)
88 stdin_channel_class = Type(BlockingStdInChannel)
89 hb_channel_class = Type(BlockingHBChannel)
90
This diff has been collapsed as it changes many lines, (512 lines changed) Show them Hide them
@@ -1,1130 +1,648
1 """Base classes to manage the interaction with a running kernel.
2
3 TODO
4 * Create logger to handle debugging and console messages.
1 """Base classes to manage a Client's interaction with a running kernel
5 2 """
6 3
7 4 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
9 6 #
10 7 # Distributed under the terms of the BSD License. The full license is in
11 8 # the file COPYING, distributed as part of this software.
12 9 #-----------------------------------------------------------------------------
13 10
14 11 #-----------------------------------------------------------------------------
15 12 # Imports
16 13 #-----------------------------------------------------------------------------
17 14
18 15 from __future__ import absolute_import
19 16
20 17 # Standard library imports
21 18 import atexit
22 19 import errno
23 import json
24 from subprocess import Popen
25 import os
26 import signal
27 import sys
28 20 from threading import Thread
29 21 import time
30 22
31 # System library imports
32 23 import zmq
33 24 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
34 25 # during garbage collection of threads at exit:
35 26 from zmq import ZMQError
36 27 from zmq.eventloop import ioloop, zmqstream
37 28
38 29 # Local imports
39 from IPython.config.configurable import Configurable
40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41 from IPython.utils.traitlets import (
42 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
43 )
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.kernel import (
46 write_connection_file,
47 make_ipkernel_cmd,
48 launch_kernel,
49 )
50 from .zmq.session import Session
51 from .kernelmanagerabc import (
30 from .channelsabc import (
52 31 ShellChannelABC, IOPubChannelABC,
53 32 HBChannelABC, StdInChannelABC,
54 KernelManagerABC
55 33 )
56 34
57
58 35 #-----------------------------------------------------------------------------
59 36 # Constants and exceptions
60 37 #-----------------------------------------------------------------------------
61 38
62 39 class InvalidPortNumber(Exception):
63 40 pass
64 41
65 42 #-----------------------------------------------------------------------------
66 43 # Utility functions
67 44 #-----------------------------------------------------------------------------
68 45
69 46 # some utilities to validate message structure, these might get moved elsewhere
70 47 # if they prove to have more generic utility
71 48
72 49 def validate_string_list(lst):
73 50 """Validate that the input is a list of strings.
74 51
75 52 Raises ValueError if not."""
76 53 if not isinstance(lst, list):
77 54 raise ValueError('input %r must be a list' % lst)
78 55 for x in lst:
79 56 if not isinstance(x, basestring):
80 57 raise ValueError('element %r in list must be a string' % x)
81 58
82 59
83 60 def validate_string_dict(dct):
84 61 """Validate that the input is a dict with string keys and values.
85 62
86 63 Raises ValueError if not."""
87 64 for k,v in dct.iteritems():
88 65 if not isinstance(k, basestring):
89 66 raise ValueError('key %r in dict must be a string' % k)
90 67 if not isinstance(v, basestring):
91 68 raise ValueError('value %r in dict must be a string' % v)
92 69
93 70
94 71 #-----------------------------------------------------------------------------
95 72 # ZMQ Socket Channel classes
96 73 #-----------------------------------------------------------------------------
97 74
98 75 class ZMQSocketChannel(Thread):
99 76 """The base class for the channels that use ZMQ sockets."""
100 77 context = None
101 78 session = None
102 79 socket = None
103 80 ioloop = None
104 81 stream = None
105 82 _address = None
106 83 _exiting = False
84 proxy_methods = []
107 85
108 86 def __init__(self, context, session, address):
109 87 """Create a channel.
110 88
111 89 Parameters
112 90 ----------
113 91 context : :class:`zmq.Context`
114 92 The ZMQ context to use.
115 93 session : :class:`session.Session`
116 94 The session to use.
117 95 address : zmq url
118 96 Standard (ip, port) tuple that the kernel is listening on.
119 97 """
120 98 super(ZMQSocketChannel, self).__init__()
121 99 self.daemon = True
122 100
123 101 self.context = context
124 102 self.session = session
125 103 if isinstance(address, tuple):
126 104 if address[1] == 0:
127 105 message = 'The port number for a channel cannot be 0.'
128 106 raise InvalidPortNumber(message)
129 107 address = "tcp://%s:%i" % address
130 108 self._address = address
131 109 atexit.register(self._notice_exit)
132 110
133 111 def _notice_exit(self):
134 112 self._exiting = True
135 113
136 114 def _run_loop(self):
137 115 """Run my loop, ignoring EINTR events in the poller"""
138 116 while True:
139 117 try:
140 118 self.ioloop.start()
141 119 except ZMQError as e:
142 120 if e.errno == errno.EINTR:
143 121 continue
144 122 else:
145 123 raise
146 124 except Exception:
147 125 if self._exiting:
148 126 break
149 127 else:
150 128 raise
151 129 else:
152 130 break
153 131
154 132 def stop(self):
155 133 """Stop the channel's event loop and join its thread.
156 134
157 135 This calls :method:`Thread.join` and returns when the thread
158 136 terminates. :class:`RuntimeError` will be raised if
159 137 :method:`self.start` is called again.
160 138 """
161 139 self.join()
162 140
163 141 @property
164 142 def address(self):
165 143 """Get the channel's address as a zmq url string.
166 144
167 145 These URLS have the form: 'tcp://127.0.0.1:5555'.
168 146 """
169 147 return self._address
170 148
171 149 def _queue_send(self, msg):
172 150 """Queue a message to be sent from the IOLoop's thread.
173 151
174 152 Parameters
175 153 ----------
176 154 msg : message to send
177 155
178 156 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
179 157 thread control of the action.
180 158 """
181 159 def thread_send():
182 160 self.session.send(self.stream, msg)
183 161 self.ioloop.add_callback(thread_send)
184 162
185 163 def _handle_recv(self, msg):
186 164 """Callback for stream.on_recv.
187 165
188 166 Unpacks message, and calls handlers with it.
189 167 """
190 168 ident,smsg = self.session.feed_identities(msg)
191 169 self.call_handlers(self.session.unserialize(smsg))
192 170
193 171
194 172
195 173 class ShellChannel(ZMQSocketChannel):
196 174 """The shell channel for issuing request/replies to the kernel."""
197 175
198 176 command_queue = None
199 177 # flag for whether execute requests should be allowed to call raw_input:
200 178 allow_stdin = True
179 proxy_methods = [
180 'execute',
181 'complete',
182 'object_info',
183 'history',
184 'kernel_info',
185 'shutdown',
186 ]
201 187
202 188 def __init__(self, context, session, address):
203 189 super(ShellChannel, self).__init__(context, session, address)
204 190 self.ioloop = ioloop.IOLoop()
205 191
206 192 def run(self):
207 193 """The thread's main activity. Call start() instead."""
208 194 self.socket = self.context.socket(zmq.DEALER)
209 195 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
210 196 self.socket.connect(self.address)
211 197 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
212 198 self.stream.on_recv(self._handle_recv)
213 199 self._run_loop()
214 200 try:
215 201 self.socket.close()
216 202 except:
217 203 pass
218 204
219 205 def stop(self):
220 206 """Stop the channel's event loop and join its thread."""
221 207 self.ioloop.stop()
222 208 super(ShellChannel, self).stop()
223 209
224 210 def call_handlers(self, msg):
225 211 """This method is called in the ioloop thread when a message arrives.
226 212
227 213 Subclasses should override this method to handle incoming messages.
228 214 It is important to remember that this method is called in the thread
229 so that some logic must be done to ensure that the application leve
215 so that some logic must be done to ensure that the application level
230 216 handlers are called in the application thread.
231 217 """
232 218 raise NotImplementedError('call_handlers must be defined in a subclass.')
233 219
234 220 def execute(self, code, silent=False, store_history=True,
235 221 user_variables=None, user_expressions=None, allow_stdin=None):
236 222 """Execute code in the kernel.
237 223
238 224 Parameters
239 225 ----------
240 226 code : str
241 227 A string of Python code.
242 228
243 229 silent : bool, optional (default False)
244 230 If set, the kernel will execute the code as quietly possible, and
245 231 will force store_history to be False.
246 232
247 233 store_history : bool, optional (default True)
248 234 If set, the kernel will store command history. This is forced
249 235 to be False if silent is True.
250 236
251 237 user_variables : list, optional
252 238 A list of variable names to pull from the user's namespace. They
253 239 will come back as a dict with these names as keys and their
254 240 :func:`repr` as values.
255 241
256 242 user_expressions : dict, optional
257 243 A dict mapping names to expressions to be evaluated in the user's
258 244 dict. The expression values are returned as strings formatted using
259 245 :func:`repr`.
260 246
261 247 allow_stdin : bool, optional (default self.allow_stdin)
262 248 Flag for whether the kernel can send stdin requests to frontends.
263 249
264 250 Some frontends (e.g. the Notebook) do not support stdin requests.
265 251 If raw_input is called from code executed from such a frontend, a
266 252 StdinNotImplementedError will be raised.
267 253
268 254 Returns
269 255 -------
270 256 The msg_id of the message sent.
271 257 """
272 258 if user_variables is None:
273 259 user_variables = []
274 260 if user_expressions is None:
275 261 user_expressions = {}
276 262 if allow_stdin is None:
277 263 allow_stdin = self.allow_stdin
278 264
279 265
280 266 # Don't waste network traffic if inputs are invalid
281 267 if not isinstance(code, basestring):
282 268 raise ValueError('code %r must be a string' % code)
283 269 validate_string_list(user_variables)
284 270 validate_string_dict(user_expressions)
285 271
286 272 # Create class for content/msg creation. Related to, but possibly
287 273 # not in Session.
288 274 content = dict(code=code, silent=silent, store_history=store_history,
289 275 user_variables=user_variables,
290 276 user_expressions=user_expressions,
291 277 allow_stdin=allow_stdin,
292 278 )
293 279 msg = self.session.msg('execute_request', content)
294 280 self._queue_send(msg)
295 281 return msg['header']['msg_id']
296 282
297 283 def complete(self, text, line, cursor_pos, block=None):
298 284 """Tab complete text in the kernel's namespace.
299 285
300 286 Parameters
301 287 ----------
302 288 text : str
303 289 The text to complete.
304 290 line : str
305 291 The full line of text that is the surrounding context for the
306 292 text to complete.
307 293 cursor_pos : int
308 294 The position of the cursor in the line where the completion was
309 295 requested.
310 296 block : str, optional
311 297 The full block of code in which the completion is being requested.
312 298
313 299 Returns
314 300 -------
315 301 The msg_id of the message sent.
316 302 """
317 303 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
318 304 msg = self.session.msg('complete_request', content)
319 305 self._queue_send(msg)
320 306 return msg['header']['msg_id']
321 307
322 308 def object_info(self, oname, detail_level=0):
323 309 """Get metadata information about an object in the kernel's namespace.
324 310
325 311 Parameters
326 312 ----------
327 313 oname : str
328 314 A string specifying the object name.
329 315 detail_level : int, optional
330 316 The level of detail for the introspection (0-2)
331 317
332 318 Returns
333 319 -------
334 320 The msg_id of the message sent.
335 321 """
336 322 content = dict(oname=oname, detail_level=detail_level)
337 323 msg = self.session.msg('object_info_request', content)
338 324 self._queue_send(msg)
339 325 return msg['header']['msg_id']
340 326
341 327 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
342 328 """Get entries from the kernel's history list.
343 329
344 330 Parameters
345 331 ----------
346 332 raw : bool
347 333 If True, return the raw input.
348 334 output : bool
349 335 If True, then return the output as well.
350 336 hist_access_type : str
351 337 'range' (fill in session, start and stop params), 'tail' (fill in n)
352 338 or 'search' (fill in pattern param).
353 339
354 340 session : int
355 341 For a range request, the session from which to get lines. Session
356 342 numbers are positive integers; negative ones count back from the
357 343 current session.
358 344 start : int
359 345 The first line number of a history range.
360 346 stop : int
361 347 The final (excluded) line number of a history range.
362 348
363 349 n : int
364 350 The number of lines of history to get for a tail request.
365 351
366 352 pattern : str
367 353 The glob-syntax pattern for a search request.
368 354
369 355 Returns
370 356 -------
371 357 The msg_id of the message sent.
372 358 """
373 359 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
374 360 **kwargs)
375 361 msg = self.session.msg('history_request', content)
376 362 self._queue_send(msg)
377 363 return msg['header']['msg_id']
378 364
379 365 def kernel_info(self):
380 366 """Request kernel info."""
381 367 msg = self.session.msg('kernel_info_request')
382 368 self._queue_send(msg)
383 369 return msg['header']['msg_id']
384 370
385 371 def shutdown(self, restart=False):
386 372 """Request an immediate kernel shutdown.
387 373
388 374 Upon receipt of the (empty) reply, client code can safely assume that
389 375 the kernel has shut down and it's safe to forcefully terminate it if
390 376 it's still alive.
391 377
392 378 The kernel will send the reply via a function registered with Python's
393 379 atexit module, ensuring it's truly done as the kernel is done with all
394 380 normal operation.
395 381 """
396 382 # Send quit message to kernel. Once we implement kernel-side setattr,
397 383 # this should probably be done that way, but for now this will do.
398 384 msg = self.session.msg('shutdown_request', {'restart':restart})
399 385 self._queue_send(msg)
400 386 return msg['header']['msg_id']
401 387
402 388
403 389
404 390 class IOPubChannel(ZMQSocketChannel):
405 391 """The iopub channel which listens for messages that the kernel publishes.
406 392
407 393 This channel is where all output is published to frontends.
408 394 """
409 395
410 396 def __init__(self, context, session, address):
411 397 super(IOPubChannel, self).__init__(context, session, address)
412 398 self.ioloop = ioloop.IOLoop()
413 399
414 400 def run(self):
415 401 """The thread's main activity. Call start() instead."""
416 402 self.socket = self.context.socket(zmq.SUB)
417 403 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
418 404 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
419 405 self.socket.connect(self.address)
420 406 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
421 407 self.stream.on_recv(self._handle_recv)
422 408 self._run_loop()
423 409 try:
424 410 self.socket.close()
425 411 except:
426 412 pass
427 413
428 414 def stop(self):
429 415 """Stop the channel's event loop and join its thread."""
430 416 self.ioloop.stop()
431 417 super(IOPubChannel, self).stop()
432 418
433 419 def call_handlers(self, msg):
434 420 """This method is called in the ioloop thread when a message arrives.
435 421
436 422 Subclasses should override this method to handle incoming messages.
437 423 It is important to remember that this method is called in the thread
438 424 so that some logic must be done to ensure that the application leve
439 425 handlers are called in the application thread.
440 426 """
441 427 raise NotImplementedError('call_handlers must be defined in a subclass.')
442 428
443 429 def flush(self, timeout=1.0):
444 430 """Immediately processes all pending messages on the iopub channel.
445 431
446 432 Callers should use this method to ensure that :method:`call_handlers`
447 433 has been called for all messages that have been received on the
448 434 0MQ SUB socket of this channel.
449 435
450 436 This method is thread safe.
451 437
452 438 Parameters
453 439 ----------
454 440 timeout : float, optional
455 441 The maximum amount of time to spend flushing, in seconds. The
456 442 default is one second.
457 443 """
458 444 # We do the IOLoop callback process twice to ensure that the IOLoop
459 445 # gets to perform at least one full poll.
460 446 stop_time = time.time() + timeout
461 447 for i in xrange(2):
462 448 self._flushed = False
463 449 self.ioloop.add_callback(self._flush)
464 450 while not self._flushed and time.time() < stop_time:
465 451 time.sleep(0.01)
466 452
467 453 def _flush(self):
468 454 """Callback for :method:`self.flush`."""
469 455 self.stream.flush()
470 456 self._flushed = True
471 457
472 458
473 459 class StdInChannel(ZMQSocketChannel):
474 460 """The stdin channel to handle raw_input requests that the kernel makes."""
475 461
476 462 msg_queue = None
463 proxy_methods = ['input']
477 464
478 465 def __init__(self, context, session, address):
479 466 super(StdInChannel, self).__init__(context, session, address)
480 467 self.ioloop = ioloop.IOLoop()
481 468
482 469 def run(self):
483 470 """The thread's main activity. Call start() instead."""
484 471 self.socket = self.context.socket(zmq.DEALER)
485 472 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
486 473 self.socket.connect(self.address)
487 474 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
488 475 self.stream.on_recv(self._handle_recv)
489 476 self._run_loop()
490 477 try:
491 478 self.socket.close()
492 479 except:
493 480 pass
494 481
495 482 def stop(self):
496 483 """Stop the channel's event loop and join its thread."""
497 484 self.ioloop.stop()
498 485 super(StdInChannel, self).stop()
499 486
500 487 def call_handlers(self, msg):
501 488 """This method is called in the ioloop thread when a message arrives.
502 489
503 490 Subclasses should override this method to handle incoming messages.
504 491 It is important to remember that this method is called in the thread
505 492 so that some logic must be done to ensure that the application leve
506 493 handlers are called in the application thread.
507 494 """
508 495 raise NotImplementedError('call_handlers must be defined in a subclass.')
509 496
510 497 def input(self, string):
511 498 """Send a string of raw input to the kernel."""
512 499 content = dict(value=string)
513 500 msg = self.session.msg('input_reply', content)
514 501 self._queue_send(msg)
515 502
516 503
517 504 class HBChannel(ZMQSocketChannel):
518 505 """The heartbeat channel which monitors the kernel heartbeat.
519 506
520 507 Note that the heartbeat channel is paused by default. As long as you start
521 508 this channel, the kernel manager will ensure that it is paused and un-paused
522 509 as appropriate.
523 510 """
524 511
525 512 time_to_dead = 3.0
526 513 socket = None
527 514 poller = None
528 515 _running = None
529 516 _pause = None
530 517 _beating = None
531 518
532 519 def __init__(self, context, session, address):
533 520 super(HBChannel, self).__init__(context, session, address)
534 521 self._running = False
535 522 self._pause =True
536 523 self.poller = zmq.Poller()
537 524
538 525 def _create_socket(self):
539 526 if self.socket is not None:
540 527 # close previous socket, before opening a new one
541 528 self.poller.unregister(self.socket)
542 529 self.socket.close()
543 530 self.socket = self.context.socket(zmq.REQ)
544 531 self.socket.setsockopt(zmq.LINGER, 0)
545 532 self.socket.connect(self.address)
546 533
547 534 self.poller.register(self.socket, zmq.POLLIN)
548 535
549 536 def _poll(self, start_time):
550 537 """poll for heartbeat replies until we reach self.time_to_dead.
551 538
552 539 Ignores interrupts, and returns the result of poll(), which
553 540 will be an empty list if no messages arrived before the timeout,
554 541 or the event tuple if there is a message to receive.
555 542 """
556 543
557 544 until_dead = self.time_to_dead - (time.time() - start_time)
558 545 # ensure poll at least once
559 546 until_dead = max(until_dead, 1e-3)
560 547 events = []
561 548 while True:
562 549 try:
563 550 events = self.poller.poll(1000 * until_dead)
564 551 except ZMQError as e:
565 552 if e.errno == errno.EINTR:
566 553 # ignore interrupts during heartbeat
567 554 # this may never actually happen
568 555 until_dead = self.time_to_dead - (time.time() - start_time)
569 556 until_dead = max(until_dead, 1e-3)
570 557 pass
571 558 else:
572 559 raise
573 560 except Exception:
574 561 if self._exiting:
575 562 break
576 563 else:
577 564 raise
578 565 else:
579 566 break
580 567 return events
581 568
582 569 def run(self):
583 570 """The thread's main activity. Call start() instead."""
584 571 self._create_socket()
585 572 self._running = True
586 573 self._beating = True
587 574
588 575 while self._running:
589 576 if self._pause:
590 577 # just sleep, and skip the rest of the loop
591 578 time.sleep(self.time_to_dead)
592 579 continue
593 580
594 581 since_last_heartbeat = 0.0
595 582 # io.rprint('Ping from HB channel') # dbg
596 583 # no need to catch EFSM here, because the previous event was
597 584 # either a recv or connect, which cannot be followed by EFSM
598 585 self.socket.send(b'ping')
599 586 request_time = time.time()
600 587 ready = self._poll(request_time)
601 588 if ready:
602 589 self._beating = True
603 590 # the poll above guarantees we have something to recv
604 591 self.socket.recv()
605 592 # sleep the remainder of the cycle
606 593 remainder = self.time_to_dead - (time.time() - request_time)
607 594 if remainder > 0:
608 595 time.sleep(remainder)
609 596 continue
610 597 else:
611 598 # nothing was received within the time limit, signal heart failure
612 599 self._beating = False
613 600 since_last_heartbeat = time.time() - request_time
614 601 self.call_handlers(since_last_heartbeat)
615 602 # and close/reopen the socket, because the REQ/REP cycle has been broken
616 603 self._create_socket()
617 604 continue
618 605 try:
619 606 self.socket.close()
620 607 except:
621 608 pass
622 609
623 610 def pause(self):
624 611 """Pause the heartbeat."""
625 612 self._pause = True
626 613
627 614 def unpause(self):
628 615 """Unpause the heartbeat."""
629 616 self._pause = False
630 617
631 618 def is_beating(self):
632 619 """Is the heartbeat running and responsive (and not paused)."""
633 620 if self.is_alive() and not self._pause and self._beating:
634 621 return True
635 622 else:
636 623 return False
637 624
638 625 def stop(self):
639 626 """Stop the channel's event loop and join its thread."""
640 627 self._running = False
641 628 super(HBChannel, self).stop()
642 629
643 630 def call_handlers(self, since_last_heartbeat):
644 631 """This method is called in the ioloop thread when a message arrives.
645 632
646 633 Subclasses should override this method to handle incoming messages.
647 634 It is important to remember that this method is called in the thread
648 635 so that some logic must be done to ensure that the application level
649 636 handlers are called in the application thread.
650 637 """
651 638 raise NotImplementedError('call_handlers must be defined in a subclass.')
652 639
653 640
654 #-----------------------------------------------------------------------------
655 # Main kernel manager class
656 #-----------------------------------------------------------------------------
657
658 class KernelManager(Configurable):
659 """Manages a single kernel on this host along with its channels.
660
661 There are four channels associated with each kernel:
662
663 * shell: for request/reply calls to the kernel.
664 * iopub: for the kernel to publish results to frontends.
665 * hb: for monitoring the kernel's heartbeat.
666 * stdin: for frontends to reply to raw_input calls in the kernel.
667
668 The usage of the channels that this class manages is optional. It is
669 entirely possible to connect to the kernels directly using ZeroMQ
670 sockets. These channels are useful primarily for talking to a kernel
671 whose :class:`KernelManager` is in the same process.
672
673 This version manages kernels started using Popen.
674 """
675 # The PyZMQ Context to use for communication with the kernel.
676 context = Instance(zmq.Context)
677 def _context_default(self):
678 return zmq.Context.instance()
679
680 # The Session to use for communication with the kernel.
681 session = Instance(Session)
682 def _session_default(self):
683 return Session(config=self.config)
684
685 # The kernel process with which the KernelManager is communicating.
686 # generally a Popen instance
687 kernel = Any()
688
689 kernel_cmd = List(Unicode, config=True,
690 help="""The Popen Command to launch the kernel.
691 Override this if you have a custom
692 """
693 )
694 def _kernel_cmd_changed(self, name, old, new):
695 self.ipython_kernel = False
696
697 ipython_kernel = Bool(True)
698
699
700 # The addresses for the communication channels.
701 connection_file = Unicode('')
702
703 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
704
705 ip = Unicode(LOCALHOST, config=True,
706 help="""Set the kernel\'s IP address [default localhost].
707 If the IP address is something other than localhost, then
708 Consoles on other machines will be able to connect
709 to the Kernel, so be careful!"""
710 )
711 def _ip_default(self):
712 if self.transport == 'ipc':
713 if self.connection_file:
714 return os.path.splitext(self.connection_file)[0] + '-ipc'
715 else:
716 return 'kernel-ipc'
717 else:
718 return LOCALHOST
719 def _ip_changed(self, name, old, new):
720 if new == '*':
721 self.ip = '0.0.0.0'
722 shell_port = Integer(0)
723 iopub_port = Integer(0)
724 stdin_port = Integer(0)
725 hb_port = Integer(0)
726
727 # The classes to use for the various channels.
728 shell_channel_class = Type(ShellChannel)
729 iopub_channel_class = Type(IOPubChannel)
730 stdin_channel_class = Type(StdInChannel)
731 hb_channel_class = Type(HBChannel)
732
733 # Protected traits.
734 _launch_args = Any
735 _shell_channel = Any
736 _iopub_channel = Any
737 _stdin_channel = Any
738 _hb_channel = Any
739 _connection_file_written=Bool(False)
740
741 def __del__(self):
742 self.cleanup_connection_file()
743
744 #--------------------------------------------------------------------------
745 # Channel management methods:
746 #--------------------------------------------------------------------------
747
748 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
749 """Starts the channels for this kernel.
750
751 This will create the channels if they do not exist and then start
752 them (their activity runs in a thread). If port numbers of 0 are
753 being used (random ports) then you must first call
754 :method:`start_kernel`. If the channels have been stopped and you
755 call this, :class:`RuntimeError` will be raised.
756 """
757 if shell:
758 self.shell_channel.start()
759 if iopub:
760 self.iopub_channel.start()
761 if stdin:
762 self.stdin_channel.start()
763 self.shell_channel.allow_stdin = True
764 else:
765 self.shell_channel.allow_stdin = False
766 if hb:
767 self.hb_channel.start()
768
769 def stop_channels(self):
770 """Stops all the running channels for this kernel.
771
772 This stops their event loops and joins their threads.
773 """
774 if self.shell_channel.is_alive():
775 self.shell_channel.stop()
776 if self.iopub_channel.is_alive():
777 self.iopub_channel.stop()
778 if self.stdin_channel.is_alive():
779 self.stdin_channel.stop()
780 if self.hb_channel.is_alive():
781 self.hb_channel.stop()
782
783 @property
784 def channels_running(self):
785 """Are any of the channels created and running?"""
786 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
787 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
788
789 def _make_url(self, port):
790 """Make a zmq url with a port.
791
792 There are two cases that this handles:
793
794 * tcp: tcp://ip:port
795 * ipc: ipc://ip-port
796 """
797 if self.transport == 'tcp':
798 return "tcp://%s:%i" % (self.ip, port)
799 else:
800 return "%s://%s-%s" % (self.transport, self.ip, port)
801
802 @property
803 def shell_channel(self):
804 """Get the shell channel object for this kernel."""
805 if self._shell_channel is None:
806 self._shell_channel = self.shell_channel_class(
807 self.context, self.session, self._make_url(self.shell_port)
808 )
809 return self._shell_channel
810
811 @property
812 def iopub_channel(self):
813 """Get the iopub channel object for this kernel."""
814 if self._iopub_channel is None:
815 self._iopub_channel = self.iopub_channel_class(
816 self.context, self.session, self._make_url(self.iopub_port)
817 )
818 return self._iopub_channel
819
820 @property
821 def stdin_channel(self):
822 """Get the stdin channel object for this kernel."""
823 if self._stdin_channel is None:
824 self._stdin_channel = self.stdin_channel_class(
825 self.context, self.session, self._make_url(self.stdin_port)
826 )
827 return self._stdin_channel
828
829 @property
830 def hb_channel(self):
831 """Get the hb channel object for this kernel."""
832 if self._hb_channel is None:
833 self._hb_channel = self.hb_channel_class(
834 self.context, self.session, self._make_url(self.hb_port)
835 )
836 return self._hb_channel
837
838 #--------------------------------------------------------------------------
839 # Connection and ipc file management
840 #--------------------------------------------------------------------------
841
842 def cleanup_connection_file(self):
843 """Cleanup connection file *if we wrote it*
844
845 Will not raise if the connection file was already removed somehow.
846 """
847 if self._connection_file_written:
848 # cleanup connection files on full shutdown of kernel we started
849 self._connection_file_written = False
850 try:
851 os.remove(self.connection_file)
852 except (IOError, OSError, AttributeError):
853 pass
854
855 def cleanup_ipc_files(self):
856 """Cleanup ipc files if we wrote them."""
857 if self.transport != 'ipc':
858 return
859 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
860 ipcfile = "%s-%i" % (self.ip, port)
861 try:
862 os.remove(ipcfile)
863 except (IOError, OSError):
864 pass
865
866 def load_connection_file(self):
867 """Load connection info from JSON dict in self.connection_file."""
868 with open(self.connection_file) as f:
869 cfg = json.loads(f.read())
870
871 from pprint import pprint
872 pprint(cfg)
873 self.transport = cfg.get('transport', 'tcp')
874 self.ip = cfg['ip']
875 self.shell_port = cfg['shell_port']
876 self.stdin_port = cfg['stdin_port']
877 self.iopub_port = cfg['iopub_port']
878 self.hb_port = cfg['hb_port']
879 self.session.key = str_to_bytes(cfg['key'])
880
881 def write_connection_file(self):
882 """Write connection info to JSON dict in self.connection_file."""
883 if self._connection_file_written:
884 return
885 self.connection_file,cfg = write_connection_file(self.connection_file,
886 transport=self.transport, ip=self.ip, key=self.session.key,
887 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
888 shell_port=self.shell_port, hb_port=self.hb_port)
889 # write_connection_file also sets default ports:
890 self.shell_port = cfg['shell_port']
891 self.stdin_port = cfg['stdin_port']
892 self.iopub_port = cfg['iopub_port']
893 self.hb_port = cfg['hb_port']
894
895 self._connection_file_written = True
896
897 #--------------------------------------------------------------------------
898 # Kernel management
899 #--------------------------------------------------------------------------
900
901 def format_kernel_cmd(self, **kw):
902 """format templated args (e.g. {connection_file})"""
903 if self.kernel_cmd:
904 cmd = self.kernel_cmd
905 else:
906 cmd = make_ipkernel_cmd(
907 'from IPython.kernel.zmq.kernelapp import main; main()',
908 **kw
909 )
910 ns = dict(connection_file=self.connection_file)
911 ns.update(self._launch_args)
912 return [ c.format(**ns) for c in cmd ]
913
914 def _launch_kernel(self, kernel_cmd, **kw):
915 """actually launch the kernel
916
917 override in a subclass to launch kernel subprocesses differently
918 """
919 return launch_kernel(kernel_cmd, **kw)
920
921 def start_kernel(self, **kw):
922 """Starts a kernel on this host in a separate process.
923
924 If random ports (port=0) are being used, this method must be called
925 before the channels are created.
926
927 Parameters:
928 -----------
929 **kw : optional
930 keyword arguments that are passed down to build the kernel_cmd
931 and launching the kernel (e.g. Popen kwargs).
932 """
933 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
934 raise RuntimeError("Can only launch a kernel on a local interface. "
935 "Make sure that the '*_address' attributes are "
936 "configured properly. "
937 "Currently valid addresses are: %s"%LOCAL_IPS
938 )
939
940 # write connection file / get default ports
941 self.write_connection_file()
942
943 # save kwargs for use in restart
944 self._launch_args = kw.copy()
945 # build the Popen cmd
946 kernel_cmd = self.format_kernel_cmd(**kw)
947 # launch the kernel subprocess
948 self.kernel = self._launch_kernel(kernel_cmd,
949 ipython_kernel=self.ipython_kernel,
950 **kw)
951
952 def shutdown_kernel(self, now=False, restart=False):
953 """Attempts to the stop the kernel process cleanly.
954
955 This attempts to shutdown the kernels cleanly by:
956
957 1. Sending it a shutdown message over the shell channel.
958 2. If that fails, the kernel is shutdown forcibly by sending it
959 a signal.
960
961 Parameters:
962 -----------
963 now : bool
964 Should the kernel be forcible killed *now*. This skips the
965 first, nice shutdown attempt.
966 restart: bool
967 Will this kernel be restarted after it is shutdown. When this
968 is True, connection files will not be cleaned up.
969 """
970 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
971 if sys.platform == 'win32':
972 self._kill_kernel()
973 return
974
975 # Pause the heart beat channel if it exists.
976 if self._hb_channel is not None:
977 self._hb_channel.pause()
978
979 if now:
980 if self.has_kernel:
981 self._kill_kernel()
982 else:
983 # Don't send any additional kernel kill messages immediately, to give
984 # the kernel a chance to properly execute shutdown actions. Wait for at
985 # most 1s, checking every 0.1s.
986 self.shell_channel.shutdown(restart=restart)
987 for i in range(10):
988 if self.is_alive:
989 time.sleep(0.1)
990 else:
991 break
992 else:
993 # OK, we've waited long enough.
994 if self.has_kernel:
995 self._kill_kernel()
996
997 if not restart:
998 self.cleanup_connection_file()
999 self.cleanup_ipc_files()
1000 else:
1001 self.cleanup_ipc_files()
1002
1003 def restart_kernel(self, now=False, **kw):
1004 """Restarts a kernel with the arguments that were used to launch it.
1005
1006 If the old kernel was launched with random ports, the same ports will be
1007 used for the new kernel. The same connection file is used again.
1008
1009 Parameters
1010 ----------
1011 now : bool, optional
1012 If True, the kernel is forcefully restarted *immediately*, without
1013 having a chance to do any cleanup action. Otherwise the kernel is
1014 given 1s to clean up before a forceful restart is issued.
1015
1016 In all cases the kernel is restarted, the only difference is whether
1017 it is given a chance to perform a clean shutdown or not.
1018
1019 **kw : optional
1020 Any options specified here will overwrite those used to launch the
1021 kernel.
1022 """
1023 if self._launch_args is None:
1024 raise RuntimeError("Cannot restart the kernel. "
1025 "No previous call to 'start_kernel'.")
1026 else:
1027 # Stop currently running kernel.
1028 self.shutdown_kernel(now=now, restart=True)
1029
1030 # Start new kernel.
1031 self._launch_args.update(kw)
1032 self.start_kernel(**self._launch_args)
1033
1034 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1035 # unless there is some delay here.
1036 if sys.platform == 'win32':
1037 time.sleep(0.2)
1038
1039 @property
1040 def has_kernel(self):
1041 """Has a kernel been started that we are managing."""
1042 return self.kernel is not None
1043
1044 def _kill_kernel(self):
1045 """Kill the running kernel.
1046
1047 This is a private method, callers should use shutdown_kernel(now=True).
1048 """
1049 if self.has_kernel:
1050 # Pause the heart beat channel if it exists.
1051 if self._hb_channel is not None:
1052 self._hb_channel.pause()
1053
1054 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1055 # TerminateProcess() on Win32).
1056 try:
1057 self.kernel.kill()
1058 except OSError as e:
1059 # In Windows, we will get an Access Denied error if the process
1060 # has already terminated. Ignore it.
1061 if sys.platform == 'win32':
1062 if e.winerror != 5:
1063 raise
1064 # On Unix, we may get an ESRCH error if the process has already
1065 # terminated. Ignore it.
1066 else:
1067 from errno import ESRCH
1068 if e.errno != ESRCH:
1069 raise
1070
1071 # Block until the kernel terminates.
1072 self.kernel.wait()
1073 self.kernel = None
1074 else:
1075 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1076
1077 def interrupt_kernel(self):
1078 """Interrupts the kernel by sending it a signal.
1079
1080 Unlike ``signal_kernel``, this operation is well supported on all
1081 platforms.
1082 """
1083 if self.has_kernel:
1084 if sys.platform == 'win32':
1085 from .zmq.parentpoller import ParentPollerWindows as Poller
1086 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1087 else:
1088 self.kernel.send_signal(signal.SIGINT)
1089 else:
1090 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1091
1092 def signal_kernel(self, signum):
1093 """Sends a signal to the kernel.
1094
1095 Note that since only SIGTERM is supported on Windows, this function is
1096 only useful on Unix systems.
1097 """
1098 if self.has_kernel:
1099 self.kernel.send_signal(signum)
1100 else:
1101 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1102
1103 @property
1104 def is_alive(self):
1105 """Is the kernel process still running?"""
1106 if self.has_kernel:
1107 if self.kernel.poll() is None:
1108 return True
1109 else:
1110 return False
1111 elif self._hb_channel is not None:
1112 # We didn't start the kernel with this KernelManager so we
1113 # use the heartbeat.
1114 return self._hb_channel.is_beating()
1115 else:
1116 # no heartbeat and not local, we can't tell if it's running,
1117 # so naively return True
1118 return True
1119
1120
1121 #-----------------------------------------------------------------------------
641 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
1122 642 # ABC Registration
1123 643 #-----------------------------------------------------------------------------
1124 644
1125 645 ShellChannelABC.register(ShellChannel)
1126 646 IOPubChannelABC.register(IOPubChannel)
1127 647 HBChannelABC.register(HBChannel)
1128 648 StdInChannelABC.register(StdInChannel)
1129 KernelManagerABC.register(KernelManager)
1130
@@ -1,347 +1,540
1 1 """Utilities for connecting to kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 from __future__ import absolute_import
21
20 22 import glob
21 23 import json
22 24 import os
23 25 import socket
24 26 import sys
25 27 from getpass import getpass
26 28 from subprocess import Popen, PIPE
27 29 import tempfile
28 30
31 import zmq
32
29 33 # external imports
30 34 from IPython.external.ssh import tunnel
31 35
32 36 # IPython imports
37 # from IPython.config import Configurable
33 38 from IPython.core.profiledir import ProfileDir
34 39 from IPython.utils.localinterfaces import LOCALHOST
35 40 from IPython.utils.path import filefind, get_ipython_dir
36 41 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
42 from IPython.utils.traitlets import (
43 Bool, Integer, Unicode, CaselessStrEnum,
44 HasTraits,
45 )
37 46
38 47
39 48 #-----------------------------------------------------------------------------
40 49 # Working with Connection Files
41 50 #-----------------------------------------------------------------------------
42 51
43 52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
44 ip=LOCALHOST, key=b'', transport='tcp'):
53 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
45 54 """Generates a JSON config file, including the selection of random ports.
46 55
47 56 Parameters
48 57 ----------
49 58
50 59 fname : unicode
51 60 The path to the file to write
52 61
53 62 shell_port : int, optional
54 The port to use for ROUTER channel.
63 The port to use for ROUTER (shell) channel.
55 64
56 65 iopub_port : int, optional
57 66 The port to use for the SUB channel.
58 67
59 68 stdin_port : int, optional
60 The port to use for the REQ (raw input) channel.
69 The port to use for the ROUTER (raw input) channel.
70
71 control_port : int, optional
72 The port to use for the ROUTER (control) channel.
61 73
62 74 hb_port : int, optional
63 The port to use for the hearbeat REP channel.
75 The port to use for the heartbeat REP channel.
64 76
65 77 ip : str, optional
66 78 The ip address the kernel will bind to.
67 79
68 80 key : str, optional
69 81 The Session key used for HMAC authentication.
70 82
71 83 """
72 84 # default to temporary connector file
73 85 if not fname:
74 86 fname = tempfile.mktemp('.json')
75 87
76 88 # Find open ports as necessary.
77 89
78 90 ports = []
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
80 int(stdin_port <= 0) + int(hb_port <= 0)
91 ports_needed = int(shell_port <= 0) + \
92 int(iopub_port <= 0) + \
93 int(stdin_port <= 0) + \
94 int(control_port <= 0) + \
95 int(hb_port <= 0)
81 96 if transport == 'tcp':
82 97 for i in range(ports_needed):
83 98 sock = socket.socket()
84 99 sock.bind(('', 0))
85 100 ports.append(sock)
86 101 for i, sock in enumerate(ports):
87 102 port = sock.getsockname()[1]
88 103 sock.close()
89 104 ports[i] = port
90 105 else:
91 106 N = 1
92 107 for i in range(ports_needed):
93 108 while os.path.exists("%s-%s" % (ip, str(N))):
94 109 N += 1
95 110 ports.append(N)
96 111 N += 1
97 112 if shell_port <= 0:
98 113 shell_port = ports.pop(0)
99 114 if iopub_port <= 0:
100 115 iopub_port = ports.pop(0)
101 116 if stdin_port <= 0:
102 117 stdin_port = ports.pop(0)
118 if control_port <= 0:
119 control_port = ports.pop(0)
103 120 if hb_port <= 0:
104 121 hb_port = ports.pop(0)
105 122
106 123 cfg = dict( shell_port=shell_port,
107 124 iopub_port=iopub_port,
108 125 stdin_port=stdin_port,
126 control_port=control_port,
109 127 hb_port=hb_port,
110 128 )
111 129 cfg['ip'] = ip
112 130 cfg['key'] = bytes_to_str(key)
113 131 cfg['transport'] = transport
114 132
115 133 with open(fname, 'w') as f:
116 134 f.write(json.dumps(cfg, indent=2))
117 135
118 136 return fname, cfg
119 137
120 138
121 139 def get_connection_file(app=None):
122 140 """Return the path to the connection file of an app
123 141
124 142 Parameters
125 143 ----------
126 144 app : IPKernelApp instance [optional]
127 145 If unspecified, the currently running app will be used
128 146 """
129 147 if app is None:
130 148 from IPython.kernel.zmq.kernelapp import IPKernelApp
131 149 if not IPKernelApp.initialized():
132 150 raise RuntimeError("app not specified, and not in a running Kernel")
133 151
134 152 app = IPKernelApp.instance()
135 153 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
136 154
137 155
138 156 def find_connection_file(filename, profile=None):
139 157 """find a connection file, and return its absolute path.
140 158
141 159 The current working directory and the profile's security
142 160 directory will be searched for the file if it is not given by
143 161 absolute path.
144 162
145 163 If profile is unspecified, then the current running application's
146 164 profile will be used, or 'default', if not run from IPython.
147 165
148 166 If the argument does not match an existing file, it will be interpreted as a
149 167 fileglob, and the matching file in the profile's security dir with
150 168 the latest access time will be used.
151 169
152 170 Parameters
153 171 ----------
154 172 filename : str
155 173 The connection file or fileglob to search for.
156 174 profile : str [optional]
157 175 The name of the profile to use when searching for the connection file,
158 176 if different from the current IPython session or 'default'.
159 177
160 178 Returns
161 179 -------
162 180 str : The absolute path of the connection file.
163 181 """
164 182 from IPython.core.application import BaseIPythonApplication as IPApp
165 183 try:
166 184 # quick check for absolute path, before going through logic
167 185 return filefind(filename)
168 186 except IOError:
169 187 pass
170 188
171 189 if profile is None:
172 190 # profile unspecified, check if running from an IPython app
173 191 if IPApp.initialized():
174 192 app = IPApp.instance()
175 193 profile_dir = app.profile_dir
176 194 else:
177 195 # not running in IPython, use default profile
178 196 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
179 197 else:
180 198 # find profiledir by profile name:
181 199 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
182 200 security_dir = profile_dir.security_dir
183 201
184 202 try:
185 203 # first, try explicit name
186 204 return filefind(filename, ['.', security_dir])
187 205 except IOError:
188 206 pass
189 207
190 208 # not found by full name
191 209
192 210 if '*' in filename:
193 211 # given as a glob already
194 212 pat = filename
195 213 else:
196 214 # accept any substring match
197 215 pat = '*%s*' % filename
198 216 matches = glob.glob( os.path.join(security_dir, pat) )
199 217 if not matches:
200 218 raise IOError("Could not find %r in %r" % (filename, security_dir))
201 219 elif len(matches) == 1:
202 220 return matches[0]
203 221 else:
204 222 # get most recent match, by access time:
205 223 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
206 224
207 225
208 226 def get_connection_info(connection_file=None, unpack=False, profile=None):
209 227 """Return the connection information for the current Kernel.
210 228
211 229 Parameters
212 230 ----------
213 231 connection_file : str [optional]
214 232 The connection file to be used. Can be given by absolute path, or
215 233 IPython will search in the security directory of a given profile.
216 234 If run from IPython,
217 235
218 236 If unspecified, the connection file for the currently running
219 237 IPython Kernel will be used, which is only allowed from inside a kernel.
220 238 unpack : bool [default: False]
221 239 if True, return the unpacked dict, otherwise just the string contents
222 240 of the file.
223 241 profile : str [optional]
224 242 The name of the profile to use when searching for the connection file,
225 243 if different from the current IPython session or 'default'.
226 244
227 245
228 246 Returns
229 247 -------
230 248 The connection dictionary of the current kernel, as string or dict,
231 249 depending on `unpack`.
232 250 """
233 251 if connection_file is None:
234 252 # get connection file from current kernel
235 253 cf = get_connection_file()
236 254 else:
237 255 # connection file specified, allow shortnames:
238 256 cf = find_connection_file(connection_file, profile=profile)
239 257
240 258 with open(cf) as f:
241 259 info = f.read()
242 260
243 261 if unpack:
244 262 info = json.loads(info)
245 263 # ensure key is bytes:
246 264 info['key'] = str_to_bytes(info.get('key', ''))
247 265 return info
248 266
249 267
250 268 def connect_qtconsole(connection_file=None, argv=None, profile=None):
251 269 """Connect a qtconsole to the current kernel.
252 270
253 271 This is useful for connecting a second qtconsole to a kernel, or to a
254 272 local notebook.
255 273
256 274 Parameters
257 275 ----------
258 276 connection_file : str [optional]
259 277 The connection file to be used. Can be given by absolute path, or
260 278 IPython will search in the security directory of a given profile.
261 279 If run from IPython,
262 280
263 281 If unspecified, the connection file for the currently running
264 282 IPython Kernel will be used, which is only allowed from inside a kernel.
265 283 argv : list [optional]
266 284 Any extra args to be passed to the console.
267 285 profile : str [optional]
268 286 The name of the profile to use when searching for the connection file,
269 287 if different from the current IPython session or 'default'.
270 288
271 289
272 290 Returns
273 291 -------
274 292 subprocess.Popen instance running the qtconsole frontend
275 293 """
276 294 argv = [] if argv is None else argv
277 295
278 296 if connection_file is None:
279 297 # get connection file from current kernel
280 298 cf = get_connection_file()
281 299 else:
282 300 cf = find_connection_file(connection_file, profile=profile)
283 301
284 302 cmd = ';'.join([
285 303 "from IPython.frontend.qt.console import qtconsoleapp",
286 304 "qtconsoleapp.main()"
287 305 ])
288 306
289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
307 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
308 stdout=PIPE, stderr=PIPE, close_fds=True,
309 )
290 310
291 311
292 312 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
293 313 """tunnel connections to a kernel via ssh
294 314
295 315 This will open four SSH tunnels from localhost on this machine to the
296 316 ports associated with the kernel. They can be either direct
297 317 localhost-localhost tunnels, or if an intermediate server is necessary,
298 318 the kernel must be listening on a public IP.
299 319
300 320 Parameters
301 321 ----------
302 322 connection_info : dict or str (path)
303 323 Either a connection dict, or the path to a JSON connection file
304 324 sshserver : str
305 325 The ssh sever to use to tunnel to the kernel. Can be a full
306 326 `user@server:port` string. ssh config aliases are respected.
307 327 sshkey : str [optional]
308 328 Path to file containing ssh key to use for authentication.
309 329 Only necessary if your ssh config does not already associate
310 330 a keyfile with the host.
311 331
312 332 Returns
313 333 -------
314 334
315 335 (shell, iopub, stdin, hb) : ints
316 336 The four ports on localhost that have been forwarded to the kernel.
317 337 """
318 338 if isinstance(connection_info, basestring):
319 339 # it's a path, unpack it
320 340 with open(connection_info) as f:
321 341 connection_info = json.loads(f.read())
322 342
323 343 cf = connection_info
324 344
325 345 lports = tunnel.select_random_ports(4)
326 346 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
327 347
328 348 remote_ip = cf['ip']
329 349
330 350 if tunnel.try_passwordless_ssh(sshserver, sshkey):
331 351 password=False
332 352 else:
333 353 password = getpass("SSH Password for %s: "%sshserver)
334 354
335 355 for lp,rp in zip(lports, rports):
336 356 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
337 357
338 358 return tuple(lports)
339 359
360
361 #-----------------------------------------------------------------------------
362 # Mixin for classes that work with connection files
363 #-----------------------------------------------------------------------------
364
365 channel_socket_types = {
366 'hb' : zmq.REQ,
367 'shell' : zmq.DEALER,
368 'iopub' : zmq.SUB,
369 'stdin' : zmq.DEALER,
370 'control': zmq.DEALER,
371 }
372
373 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
374
375 class ConnectionFileMixin(HasTraits):
376 """Mixin for configurable classes that work with connection files"""
377
378 # The addresses for the communication channels
379 connection_file = Unicode('')
380 _connection_file_written = Bool(False)
381
382 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
383
384 ip = Unicode(LOCALHOST, config=True,
385 help="""Set the kernel\'s IP address [default localhost].
386 If the IP address is something other than localhost, then
387 Consoles on other machines will be able to connect
388 to the Kernel, so be careful!"""
389 )
390
391 def _ip_default(self):
392 if self.transport == 'ipc':
393 if self.connection_file:
394 return os.path.splitext(self.connection_file)[0] + '-ipc'
395 else:
396 return 'kernel-ipc'
397 else:
398 return LOCALHOST
399
400 def _ip_changed(self, name, old, new):
401 if new == '*':
402 self.ip = '0.0.0.0'
403
404 # protected traits
405
406 shell_port = Integer(0)
407 iopub_port = Integer(0)
408 stdin_port = Integer(0)
409 control_port = Integer(0)
410 hb_port = Integer(0)
411
412 @property
413 def ports(self):
414 return [ getattr(self, name) for name in port_names ]
415
416 #--------------------------------------------------------------------------
417 # Connection and ipc file management
418 #--------------------------------------------------------------------------
419
420 def get_connection_info(self):
421 """return the connection info as a dict"""
422 return dict(
423 transport=self.transport,
424 ip=self.ip,
425 shell_port=self.shell_port,
426 iopub_port=self.iopub_port,
427 stdin_port=self.stdin_port,
428 hb_port=self.hb_port,
429 control_port=self.control_port,
430 )
431
432 def cleanup_connection_file(self):
433 """Cleanup connection file *if we wrote it*
434
435 Will not raise if the connection file was already removed somehow.
436 """
437 if self._connection_file_written:
438 # cleanup connection files on full shutdown of kernel we started
439 self._connection_file_written = False
440 try:
441 os.remove(self.connection_file)
442 except (IOError, OSError, AttributeError):
443 pass
444
445 def cleanup_ipc_files(self):
446 """Cleanup ipc files if we wrote them."""
447 if self.transport != 'ipc':
448 return
449 for port in self.ports:
450 ipcfile = "%s-%i" % (self.ip, port)
451 try:
452 os.remove(ipcfile)
453 except (IOError, OSError):
454 pass
455
456 def write_connection_file(self):
457 """Write connection info to JSON dict in self.connection_file."""
458 if self._connection_file_written:
459 return
460
461 self.connection_file, cfg = write_connection_file(self.connection_file,
462 transport=self.transport, ip=self.ip, key=self.session.key,
463 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
464 shell_port=self.shell_port, hb_port=self.hb_port,
465 control_port=self.control_port,
466 )
467 # write_connection_file also sets default ports:
468 for name in port_names:
469 setattr(self, name, cfg[name])
470
471 self._connection_file_written = True
472
473 def load_connection_file(self):
474 """Load connection info from JSON dict in self.connection_file."""
475 with open(self.connection_file) as f:
476 cfg = json.loads(f.read())
477
478 self.transport = cfg.get('transport', 'tcp')
479 self.ip = cfg['ip']
480 for name in port_names:
481 setattr(self, name, cfg[name])
482 self.session.key = str_to_bytes(cfg['key'])
483
484 #--------------------------------------------------------------------------
485 # Creating connected sockets
486 #--------------------------------------------------------------------------
487
488 def _make_url(self, channel):
489 """Make a ZeroMQ URL for a given channel."""
490 transport = self.transport
491 ip = self.ip
492 port = getattr(self, '%s_port' % channel)
493
494 if transport == 'tcp':
495 return "tcp://%s:%i" % (ip, port)
496 else:
497 return "%s://%s-%s" % (transport, ip, port)
498
499 def _create_connected_socket(self, channel, identity=None):
500 """Create a zmq Socket and connect it to the kernel."""
501 url = self._make_url(channel)
502 socket_type = channel_socket_types[channel]
503 self.log.info("Connecting to: %s" % url)
504 sock = self.context.socket(socket_type)
505 if identity:
506 sock.identity = identity
507 sock.connect(url)
508 return sock
509
510 def connect_iopub(self, identity=None):
511 """return zmq Socket connected to the IOPub channel"""
512 sock = self._create_connected_socket('iopub', identity=identity)
513 sock.setsockopt(zmq.SUBSCRIBE, b'')
514 return sock
515
516 def connect_shell(self, identity=None):
517 """return zmq Socket connected to the Shell channel"""
518 return self._create_connected_socket('shell', identity=identity)
519
520 def connect_stdin(self, identity=None):
521 """return zmq Socket connected to the StdIn channel"""
522 return self._create_connected_socket('stdin', identity=identity)
523
524 def connect_hb(self, identity=None):
525 """return zmq Socket connected to the Heartbeat channel"""
526 return self._create_connected_socket('hb', identity=identity)
527
528 def connect_control(self, identity=None):
529 """return zmq Socket connected to the Heartbeat channel"""
530 return self._create_connected_socket('control', identity=identity)
531
532
340 533 __all__ = [
341 534 'write_connection_file',
342 535 'get_connection_file',
343 536 'find_connection_file',
344 537 'get_connection_info',
345 538 'connect_qtconsole',
346 539 'tunnel_to_kernel',
347 540 ]
@@ -0,0 +1,10
1 from .channels import (
2 InProcessShellChannel,
3 InProcessIOPubChannel,
4 InProcessStdInChannel,
5 InProcessHBChannel,
6 )
7
8 from .client import InProcessKernelClient
9 from .manager import InProcessKernelManager
10 from .blocking import BlockingInProcessKernelClient
1 NO CONTENT: file renamed from IPython/kernel/inprocess/blockingkernelmanager.py to IPython/kernel/inprocess/blocking.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/kernel/inprocess/kernelmanager.py to IPython/kernel/inprocess/channels.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/kernel/kernelmanagerabc.py to IPython/kernel/managerabc.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now