##// END OF EJS Templates
Merge pull request #1008 from minrk/excepthook...
Fernando Perez -
r5348:493f6d4b merge
parent child Browse files
Show More
@@ -1,319 +1,336 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import atexit
30 import atexit
31 import glob
31 import glob
32 import logging
32 import logging
33 import os
33 import os
34 import shutil
34 import shutil
35 import sys
35 import sys
36
36
37 from IPython.config.application import Application, catch_config_error
37 from IPython.config.application import Application, catch_config_error
38 from IPython.config.configurable import Configurable
38 from IPython.config.configurable import Configurable
39 from IPython.config.loader import Config, ConfigFileNotFound
39 from IPython.config.loader import Config, ConfigFileNotFound
40 from IPython.core import release, crashhandler
40 from IPython.core import release, crashhandler
41 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 from IPython.core.profiledir import ProfileDir, ProfileDirError
42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
43 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
44 from IPython.utils import py3compat
44 from IPython.utils import py3compat
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Classes and functions
47 # Classes and functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Base Application Class
52 # Base Application Class
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 # aliases and flags
55 # aliases and flags
56
56
57 base_aliases = {
57 base_aliases = {
58 'profile' : 'BaseIPythonApplication.profile',
58 'profile' : 'BaseIPythonApplication.profile',
59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
59 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 'log-level' : 'Application.log_level',
60 'log-level' : 'Application.log_level',
61 }
61 }
62
62
63 base_flags = dict(
63 base_flags = dict(
64 debug = ({'Application' : {'log_level' : logging.DEBUG}},
64 debug = ({'Application' : {'log_level' : logging.DEBUG}},
65 "set log level to logging.DEBUG (maximize logging output)"),
65 "set log level to logging.DEBUG (maximize logging output)"),
66 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
66 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
67 "set log level to logging.CRITICAL (minimize logging output)"),
67 "set log level to logging.CRITICAL (minimize logging output)"),
68 init = ({'BaseIPythonApplication' : {
68 init = ({'BaseIPythonApplication' : {
69 'copy_config_files' : True,
69 'copy_config_files' : True,
70 'auto_create' : True}
70 'auto_create' : True}
71 }, """Initialize profile with default config files. This is equivalent
71 }, """Initialize profile with default config files. This is equivalent
72 to running `ipython profile create <profile>` prior to startup.
72 to running `ipython profile create <profile>` prior to startup.
73 """)
73 """)
74 )
74 )
75
75
76
76
77 class BaseIPythonApplication(Application):
77 class BaseIPythonApplication(Application):
78
78
79 name = Unicode(u'ipython')
79 name = Unicode(u'ipython')
80 description = Unicode(u'IPython: an enhanced interactive Python shell.')
80 description = Unicode(u'IPython: an enhanced interactive Python shell.')
81 version = Unicode(release.version)
81 version = Unicode(release.version)
82
82
83 aliases = Dict(base_aliases)
83 aliases = Dict(base_aliases)
84 flags = Dict(base_flags)
84 flags = Dict(base_flags)
85 classes = List([ProfileDir])
85 classes = List([ProfileDir])
86
86
87 # Track whether the config_file has changed,
87 # Track whether the config_file has changed,
88 # because some logic happens only if we aren't using the default.
88 # because some logic happens only if we aren't using the default.
89 config_file_specified = Bool(False)
89 config_file_specified = Bool(False)
90
90
91 config_file_name = Unicode(u'ipython_config.py')
91 config_file_name = Unicode(u'ipython_config.py')
92 def _config_file_name_default(self):
92 def _config_file_name_default(self):
93 return self.name.replace('-','_') + u'_config.py'
93 return self.name.replace('-','_') + u'_config.py'
94 def _config_file_name_changed(self, name, old, new):
94 def _config_file_name_changed(self, name, old, new):
95 if new != old:
95 if new != old:
96 self.config_file_specified = True
96 self.config_file_specified = True
97
97
98 # The directory that contains IPython's builtin profiles.
98 # The directory that contains IPython's builtin profiles.
99 builtin_profile_dir = Unicode(
99 builtin_profile_dir = Unicode(
100 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
100 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
101 )
101 )
102
102
103 config_file_paths = List(Unicode)
103 config_file_paths = List(Unicode)
104 def _config_file_paths_default(self):
104 def _config_file_paths_default(self):
105 return [os.getcwdu()]
105 return [os.getcwdu()]
106
106
107 profile = Unicode(u'default', config=True,
107 profile = Unicode(u'default', config=True,
108 help="""The IPython profile to use."""
108 help="""The IPython profile to use."""
109 )
109 )
110
110
111 def _profile_changed(self, name, old, new):
111 def _profile_changed(self, name, old, new):
112 self.builtin_profile_dir = os.path.join(
112 self.builtin_profile_dir = os.path.join(
113 get_ipython_package_dir(), u'config', u'profile', new
113 get_ipython_package_dir(), u'config', u'profile', new
114 )
114 )
115
115
116 ipython_dir = Unicode(get_ipython_dir(), config=True,
116 ipython_dir = Unicode(get_ipython_dir(), config=True,
117 help="""
117 help="""
118 The name of the IPython directory. This directory is used for logging
118 The name of the IPython directory. This directory is used for logging
119 configuration (through profiles), history storage, etc. The default
119 configuration (through profiles), history storage, etc. The default
120 is usually $HOME/.ipython. This options can also be specified through
120 is usually $HOME/.ipython. This options can also be specified through
121 the environment variable IPYTHON_DIR.
121 the environment variable IPYTHON_DIR.
122 """
122 """
123 )
123 )
124
124
125 overwrite = Bool(False, config=True,
125 overwrite = Bool(False, config=True,
126 help="""Whether to overwrite existing config files when copying""")
126 help="""Whether to overwrite existing config files when copying""")
127 auto_create = Bool(False, config=True,
127 auto_create = Bool(False, config=True,
128 help="""Whether to create profile dir if it doesn't exist""")
128 help="""Whether to create profile dir if it doesn't exist""")
129
129
130 config_files = List(Unicode)
130 config_files = List(Unicode)
131 def _config_files_default(self):
131 def _config_files_default(self):
132 return [u'ipython_config.py']
132 return [u'ipython_config.py']
133
133
134 copy_config_files = Bool(False, config=True,
134 copy_config_files = Bool(False, config=True,
135 help="""Whether to install the default config files into the profile dir.
135 help="""Whether to install the default config files into the profile dir.
136 If a new profile is being created, and IPython contains config files for that
136 If a new profile is being created, and IPython contains config files for that
137 profile, then they will be staged into the new directory. Otherwise,
137 profile, then they will be staged into the new directory. Otherwise,
138 default config files will be automatically generated.
138 default config files will be automatically generated.
139 """)
139 """)
140
141 verbose_crash = Bool(False, config=True,
142 help="""Create a massive crash report when IPython enconters what may be an
143 internal error. The default is to append a short message to the
144 usual traceback""")
140
145
141 # The class to use as the crash handler.
146 # The class to use as the crash handler.
142 crash_handler_class = Type(crashhandler.CrashHandler)
147 crash_handler_class = Type(crashhandler.CrashHandler)
143
148
144 def __init__(self, **kwargs):
149 def __init__(self, **kwargs):
145 super(BaseIPythonApplication, self).__init__(**kwargs)
150 super(BaseIPythonApplication, self).__init__(**kwargs)
146 # ensure even default IPYTHON_DIR exists
151 # ensure even default IPYTHON_DIR exists
147 if not os.path.exists(self.ipython_dir):
152 if not os.path.exists(self.ipython_dir):
148 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
153 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
149
154
150 #-------------------------------------------------------------------------
155 #-------------------------------------------------------------------------
151 # Various stages of Application creation
156 # Various stages of Application creation
152 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
153
158
154 def init_crash_handler(self):
159 def init_crash_handler(self):
155 """Create a crash handler, typically setting sys.excepthook to it."""
160 """Create a crash handler, typically setting sys.excepthook to it."""
156 self.crash_handler = self.crash_handler_class(self)
161 self.crash_handler = self.crash_handler_class(self)
157 sys.excepthook = self.crash_handler
162 sys.excepthook = self.excepthook
158 def unset_crashhandler():
163 def unset_crashhandler():
159 sys.excepthook = sys.__excepthook__
164 sys.excepthook = sys.__excepthook__
160 atexit.register(unset_crashhandler)
165 atexit.register(unset_crashhandler)
161
166
167 def excepthook(self, etype, evalue, tb):
168 """this is sys.excepthook after init_crashhandler
169
170 set self.verbose_crash=True to use our full crashhandler, instead of
171 a regular traceback with a short message (crash_handler_lite)
172 """
173
174 if self.verbose_crash:
175 return self.crash_handler(etype, evalue, tb)
176 else:
177 return crashhandler.crash_handler_lite(etype, evalue, tb)
178
162 def _ipython_dir_changed(self, name, old, new):
179 def _ipython_dir_changed(self, name, old, new):
163 if old in sys.path:
180 if old in sys.path:
164 sys.path.remove(old)
181 sys.path.remove(old)
165 sys.path.append(os.path.abspath(new))
182 sys.path.append(os.path.abspath(new))
166 if not os.path.isdir(new):
183 if not os.path.isdir(new):
167 os.makedirs(new, mode=0777)
184 os.makedirs(new, mode=0777)
168 readme = os.path.join(new, 'README')
185 readme = os.path.join(new, 'README')
169 if not os.path.exists(readme):
186 if not os.path.exists(readme):
170 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
187 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
171 shutil.copy(os.path.join(path, 'README'), readme)
188 shutil.copy(os.path.join(path, 'README'), readme)
172 self.log.debug("IPYTHON_DIR set to: %s" % new)
189 self.log.debug("IPYTHON_DIR set to: %s" % new)
173
190
174 def load_config_file(self, suppress_errors=True):
191 def load_config_file(self, suppress_errors=True):
175 """Load the config file.
192 """Load the config file.
176
193
177 By default, errors in loading config are handled, and a warning
194 By default, errors in loading config are handled, and a warning
178 printed on screen. For testing, the suppress_errors option is set
195 printed on screen. For testing, the suppress_errors option is set
179 to False, so errors will make tests fail.
196 to False, so errors will make tests fail.
180 """
197 """
181 self.log.debug("Searching path %s for config files", self.config_file_paths)
198 self.log.debug("Searching path %s for config files", self.config_file_paths)
182 base_config = 'ipython_config.py'
199 base_config = 'ipython_config.py'
183 self.log.debug("Attempting to load config file: %s" %
200 self.log.debug("Attempting to load config file: %s" %
184 base_config)
201 base_config)
185 try:
202 try:
186 Application.load_config_file(
203 Application.load_config_file(
187 self,
204 self,
188 base_config,
205 base_config,
189 path=self.config_file_paths
206 path=self.config_file_paths
190 )
207 )
191 except ConfigFileNotFound:
208 except ConfigFileNotFound:
192 # ignore errors loading parent
209 # ignore errors loading parent
193 self.log.debug("Config file %s not found", base_config)
210 self.log.debug("Config file %s not found", base_config)
194 pass
211 pass
195 if self.config_file_name == base_config:
212 if self.config_file_name == base_config:
196 # don't load secondary config
213 # don't load secondary config
197 return
214 return
198 self.log.debug("Attempting to load config file: %s" %
215 self.log.debug("Attempting to load config file: %s" %
199 self.config_file_name)
216 self.config_file_name)
200 try:
217 try:
201 Application.load_config_file(
218 Application.load_config_file(
202 self,
219 self,
203 self.config_file_name,
220 self.config_file_name,
204 path=self.config_file_paths
221 path=self.config_file_paths
205 )
222 )
206 except ConfigFileNotFound:
223 except ConfigFileNotFound:
207 # Only warn if the default config file was NOT being used.
224 # Only warn if the default config file was NOT being used.
208 if self.config_file_specified:
225 if self.config_file_specified:
209 msg = self.log.warn
226 msg = self.log.warn
210 else:
227 else:
211 msg = self.log.debug
228 msg = self.log.debug
212 msg("Config file not found, skipping: %s", self.config_file_name)
229 msg("Config file not found, skipping: %s", self.config_file_name)
213 except:
230 except:
214 # For testing purposes.
231 # For testing purposes.
215 if not suppress_errors:
232 if not suppress_errors:
216 raise
233 raise
217 self.log.warn("Error loading config file: %s" %
234 self.log.warn("Error loading config file: %s" %
218 self.config_file_name, exc_info=True)
235 self.config_file_name, exc_info=True)
219
236
220 def init_profile_dir(self):
237 def init_profile_dir(self):
221 """initialize the profile dir"""
238 """initialize the profile dir"""
222 try:
239 try:
223 # location explicitly specified:
240 # location explicitly specified:
224 location = self.config.ProfileDir.location
241 location = self.config.ProfileDir.location
225 except AttributeError:
242 except AttributeError:
226 # location not specified, find by profile name
243 # location not specified, find by profile name
227 try:
244 try:
228 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
245 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
229 except ProfileDirError:
246 except ProfileDirError:
230 # not found, maybe create it (always create default profile)
247 # not found, maybe create it (always create default profile)
231 if self.auto_create or self.profile=='default':
248 if self.auto_create or self.profile=='default':
232 try:
249 try:
233 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
250 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
234 except ProfileDirError:
251 except ProfileDirError:
235 self.log.fatal("Could not create profile: %r"%self.profile)
252 self.log.fatal("Could not create profile: %r"%self.profile)
236 self.exit(1)
253 self.exit(1)
237 else:
254 else:
238 self.log.info("Created profile dir: %r"%p.location)
255 self.log.info("Created profile dir: %r"%p.location)
239 else:
256 else:
240 self.log.fatal("Profile %r not found."%self.profile)
257 self.log.fatal("Profile %r not found."%self.profile)
241 self.exit(1)
258 self.exit(1)
242 else:
259 else:
243 self.log.info("Using existing profile dir: %r"%p.location)
260 self.log.info("Using existing profile dir: %r"%p.location)
244 else:
261 else:
245 # location is fully specified
262 # location is fully specified
246 try:
263 try:
247 p = ProfileDir.find_profile_dir(location, self.config)
264 p = ProfileDir.find_profile_dir(location, self.config)
248 except ProfileDirError:
265 except ProfileDirError:
249 # not found, maybe create it
266 # not found, maybe create it
250 if self.auto_create:
267 if self.auto_create:
251 try:
268 try:
252 p = ProfileDir.create_profile_dir(location, self.config)
269 p = ProfileDir.create_profile_dir(location, self.config)
253 except ProfileDirError:
270 except ProfileDirError:
254 self.log.fatal("Could not create profile directory: %r"%location)
271 self.log.fatal("Could not create profile directory: %r"%location)
255 self.exit(1)
272 self.exit(1)
256 else:
273 else:
257 self.log.info("Creating new profile dir: %r"%location)
274 self.log.info("Creating new profile dir: %r"%location)
258 else:
275 else:
259 self.log.fatal("Profile directory %r not found."%location)
276 self.log.fatal("Profile directory %r not found."%location)
260 self.exit(1)
277 self.exit(1)
261 else:
278 else:
262 self.log.info("Using existing profile dir: %r"%location)
279 self.log.info("Using existing profile dir: %r"%location)
263
280
264 self.profile_dir = p
281 self.profile_dir = p
265 self.config_file_paths.append(p.location)
282 self.config_file_paths.append(p.location)
266
283
267 def init_config_files(self):
284 def init_config_files(self):
268 """[optionally] copy default config files into profile dir."""
285 """[optionally] copy default config files into profile dir."""
269 # copy config files
286 # copy config files
270 path = self.builtin_profile_dir
287 path = self.builtin_profile_dir
271 if self.copy_config_files:
288 if self.copy_config_files:
272 src = self.profile
289 src = self.profile
273
290
274 cfg = self.config_file_name
291 cfg = self.config_file_name
275 if path and os.path.exists(os.path.join(path, cfg)):
292 if path and os.path.exists(os.path.join(path, cfg)):
276 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
293 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
277 cfg, src, self.profile_dir.location, self.overwrite)
294 cfg, src, self.profile_dir.location, self.overwrite)
278 )
295 )
279 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
296 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
280 else:
297 else:
281 self.stage_default_config_file()
298 self.stage_default_config_file()
282 else:
299 else:
283 # Still stage *bundled* config files, but not generated ones
300 # Still stage *bundled* config files, but not generated ones
284 # This is necessary for `ipython profile=sympy` to load the profile
301 # This is necessary for `ipython profile=sympy` to load the profile
285 # on the first go
302 # on the first go
286 files = glob.glob(os.path.join(path, '*.py'))
303 files = glob.glob(os.path.join(path, '*.py'))
287 for fullpath in files:
304 for fullpath in files:
288 cfg = os.path.basename(fullpath)
305 cfg = os.path.basename(fullpath)
289 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
306 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
290 # file was copied
307 # file was copied
291 self.log.warn("Staging bundled %s from %s into %r"%(
308 self.log.warn("Staging bundled %s from %s into %r"%(
292 cfg, self.profile, self.profile_dir.location)
309 cfg, self.profile, self.profile_dir.location)
293 )
310 )
294
311
295
312
296 def stage_default_config_file(self):
313 def stage_default_config_file(self):
297 """auto generate default config file, and stage it into the profile."""
314 """auto generate default config file, and stage it into the profile."""
298 s = self.generate_config_file()
315 s = self.generate_config_file()
299 fname = os.path.join(self.profile_dir.location, self.config_file_name)
316 fname = os.path.join(self.profile_dir.location, self.config_file_name)
300 if self.overwrite or not os.path.exists(fname):
317 if self.overwrite or not os.path.exists(fname):
301 self.log.warn("Generating default config file: %r"%(fname))
318 self.log.warn("Generating default config file: %r"%(fname))
302 with open(fname, 'w') as f:
319 with open(fname, 'w') as f:
303 f.write(s)
320 f.write(s)
304
321
305 @catch_config_error
322 @catch_config_error
306 def initialize(self, argv=None):
323 def initialize(self, argv=None):
307 # don't hook up crash handler before parsing command-line
324 # don't hook up crash handler before parsing command-line
308 self.parse_command_line(argv)
325 self.parse_command_line(argv)
309 self.init_crash_handler()
326 self.init_crash_handler()
310 if self.subapp is not None:
327 if self.subapp is not None:
311 # stop here if subapp is taking over
328 # stop here if subapp is taking over
312 return
329 return
313 cl_config = self.config
330 cl_config = self.config
314 self.init_profile_dir()
331 self.init_profile_dir()
315 self.init_config_files()
332 self.init_config_files()
316 self.load_config_file()
333 self.load_config_file()
317 # enforce cl-opts override configfile opts:
334 # enforce cl-opts override configfile opts:
318 self.update_config(cl_config)
335 self.update_config(cl_config)
319
336
@@ -1,186 +1,211 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian E. Granger
7 * Brian E. Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 # Copyright (C) 2008-2010 The IPython Development Team
12 # Copyright (C) 2008-2010 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import sys
23 import sys
24 import traceback
24 from pprint import pformat
25 from pprint import pformat
25
26
26 from IPython.core import ultratb
27 from IPython.core import ultratb
28 from IPython.core.release import author_email
27 from IPython.utils.sysinfo import sys_info
29 from IPython.utils.sysinfo import sys_info
28
30
29 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
30 # Code
32 # Code
31 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
32
34
33 # Template for the user message.
35 # Template for the user message.
34 _default_message_template = """\
36 _default_message_template = """\
35 Oops, {app_name} crashed. We do our best to make it stable, but...
37 Oops, {app_name} crashed. We do our best to make it stable, but...
36
38
37 A crash report was automatically generated with the following information:
39 A crash report was automatically generated with the following information:
38 - A verbatim copy of the crash traceback.
40 - A verbatim copy of the crash traceback.
39 - A copy of your input history during this session.
41 - A copy of your input history during this session.
40 - Data on your current {app_name} configuration.
42 - Data on your current {app_name} configuration.
41
43
42 It was left in the file named:
44 It was left in the file named:
43 \t'{crash_report_fname}'
45 \t'{crash_report_fname}'
44 If you can email this file to the developers, the information in it will help
46 If you can email this file to the developers, the information in it will help
45 them in understanding and correcting the problem.
47 them in understanding and correcting the problem.
46
48
47 You can mail it to: {contact_name} at {contact_email}
49 You can mail it to: {contact_name} at {contact_email}
48 with the subject '{app_name} Crash Report'.
50 with the subject '{app_name} Crash Report'.
49
51
50 If you want to do it now, the following command will work (under Unix):
52 If you want to do it now, the following command will work (under Unix):
51 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
53 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
52
54
53 To ensure accurate tracking of this issue, please file a report about it at:
55 To ensure accurate tracking of this issue, please file a report about it at:
54 {bug_tracker}
56 {bug_tracker}
55 """
57 """
56
58
59 _lite_message_template = """
60 If you suspect this is an IPython bug, please report it at:
61 https://github.com/ipython/ipython/issues
62 or send an email to the mailing list at {email}
63
64 You can enable a much more verbose traceback with:
65 {config}Application.verbose_crash=True
66 """
67
57
68
58 class CrashHandler(object):
69 class CrashHandler(object):
59 """Customizable crash handlers for IPython applications.
70 """Customizable crash handlers for IPython applications.
60
71
61 Instances of this class provide a :meth:`__call__` method which can be
72 Instances of this class provide a :meth:`__call__` method which can be
62 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
73 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
63
74
64 def __call__(self, etype, evalue, etb)
75 def __call__(self, etype, evalue, etb)
65 """
76 """
66
77
67 message_template = _default_message_template
78 message_template = _default_message_template
68 section_sep = '\n\n'+'*'*75+'\n\n'
79 section_sep = '\n\n'+'*'*75+'\n\n'
69
80
70 def __init__(self, app, contact_name=None, contact_email=None,
81 def __init__(self, app, contact_name=None, contact_email=None,
71 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
82 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
72 """Create a new crash handler
83 """Create a new crash handler
73
84
74 Parameters
85 Parameters
75 ----------
86 ----------
76 app : Application
87 app : Application
77 A running :class:`Application` instance, which will be queried at
88 A running :class:`Application` instance, which will be queried at
78 crash time for internal information.
89 crash time for internal information.
79
90
80 contact_name : str
91 contact_name : str
81 A string with the name of the person to contact.
92 A string with the name of the person to contact.
82
93
83 contact_email : str
94 contact_email : str
84 A string with the email address of the contact.
95 A string with the email address of the contact.
85
96
86 bug_tracker : str
97 bug_tracker : str
87 A string with the URL for your project's bug tracker.
98 A string with the URL for your project's bug tracker.
88
99
89 show_crash_traceback : bool
100 show_crash_traceback : bool
90 If false, don't print the crash traceback on stderr, only generate
101 If false, don't print the crash traceback on stderr, only generate
91 the on-disk report
102 the on-disk report
92
103
93 Non-argument instance attributes:
104 Non-argument instance attributes:
94
105
95 These instances contain some non-argument attributes which allow for
106 These instances contain some non-argument attributes which allow for
96 further customization of the crash handler's behavior. Please see the
107 further customization of the crash handler's behavior. Please see the
97 source for further details.
108 source for further details.
98 """
109 """
99 self.crash_report_fname = "Crash_report_%s.txt" % app.name
110 self.crash_report_fname = "Crash_report_%s.txt" % app.name
100 self.app = app
111 self.app = app
101 self.call_pdb = call_pdb
112 self.call_pdb = call_pdb
102 #self.call_pdb = True # dbg
113 #self.call_pdb = True # dbg
103 self.show_crash_traceback = show_crash_traceback
114 self.show_crash_traceback = show_crash_traceback
104 self.info = dict(app_name = app.name,
115 self.info = dict(app_name = app.name,
105 contact_name = contact_name,
116 contact_name = contact_name,
106 contact_email = contact_email,
117 contact_email = contact_email,
107 bug_tracker = bug_tracker,
118 bug_tracker = bug_tracker,
108 crash_report_fname = self.crash_report_fname)
119 crash_report_fname = self.crash_report_fname)
109
120
110
121
111 def __call__(self, etype, evalue, etb):
122 def __call__(self, etype, evalue, etb):
112 """Handle an exception, call for compatible with sys.excepthook"""
123 """Handle an exception, call for compatible with sys.excepthook"""
113
124
114 # do not allow the crash handler to be called twice without reinstalling it
125 # do not allow the crash handler to be called twice without reinstalling it
115 # this prevents unlikely errors in the crash handling from entering an
126 # this prevents unlikely errors in the crash handling from entering an
116 # infinite loop.
127 # infinite loop.
117 sys.excepthook = sys.__excepthook__
128 sys.excepthook = sys.__excepthook__
118
129
119 # Report tracebacks shouldn't use color in general (safer for users)
130 # Report tracebacks shouldn't use color in general (safer for users)
120 color_scheme = 'NoColor'
131 color_scheme = 'NoColor'
121
132
122 # Use this ONLY for developer debugging (keep commented out for release)
133 # Use this ONLY for developer debugging (keep commented out for release)
123 #color_scheme = 'Linux' # dbg
134 #color_scheme = 'Linux' # dbg
124 try:
135 try:
125 rptdir = self.app.ipython_dir
136 rptdir = self.app.ipython_dir
126 except:
137 except:
127 rptdir = os.getcwdu()
138 rptdir = os.getcwdu()
128 if rptdir is None or not os.path.isdir(rptdir):
139 if rptdir is None or not os.path.isdir(rptdir):
129 rptdir = os.getcwdu()
140 rptdir = os.getcwdu()
130 report_name = os.path.join(rptdir,self.crash_report_fname)
141 report_name = os.path.join(rptdir,self.crash_report_fname)
131 # write the report filename into the instance dict so it can get
142 # write the report filename into the instance dict so it can get
132 # properly expanded out in the user message template
143 # properly expanded out in the user message template
133 self.crash_report_fname = report_name
144 self.crash_report_fname = report_name
134 self.info['crash_report_fname'] = report_name
145 self.info['crash_report_fname'] = report_name
135 TBhandler = ultratb.VerboseTB(
146 TBhandler = ultratb.VerboseTB(
136 color_scheme=color_scheme,
147 color_scheme=color_scheme,
137 long_header=1,
148 long_header=1,
138 call_pdb=self.call_pdb,
149 call_pdb=self.call_pdb,
139 )
150 )
140 if self.call_pdb:
151 if self.call_pdb:
141 TBhandler(etype,evalue,etb)
152 TBhandler(etype,evalue,etb)
142 return
153 return
143 else:
154 else:
144 traceback = TBhandler.text(etype,evalue,etb,context=31)
155 traceback = TBhandler.text(etype,evalue,etb,context=31)
145
156
146 # print traceback to screen
157 # print traceback to screen
147 if self.show_crash_traceback:
158 if self.show_crash_traceback:
148 print >> sys.stderr, traceback
159 print >> sys.stderr, traceback
149
160
150 # and generate a complete report on disk
161 # and generate a complete report on disk
151 try:
162 try:
152 report = open(report_name,'w')
163 report = open(report_name,'w')
153 except:
164 except:
154 print >> sys.stderr, 'Could not create crash report on disk.'
165 print >> sys.stderr, 'Could not create crash report on disk.'
155 return
166 return
156
167
157 # Inform user on stderr of what happened
168 # Inform user on stderr of what happened
158 print >> sys.stderr, '\n'+'*'*70+'\n'
169 print >> sys.stderr, '\n'+'*'*70+'\n'
159 print >> sys.stderr, self.message_template.format(**self.info)
170 print >> sys.stderr, self.message_template.format(**self.info)
160
171
161 # Construct report on disk
172 # Construct report on disk
162 report.write(self.make_report(traceback))
173 report.write(self.make_report(traceback))
163 report.close()
174 report.close()
164 raw_input("Hit <Enter> to quit this message (your terminal may close):")
175 raw_input("Hit <Enter> to quit (your terminal may close):")
165
176
166 def make_report(self,traceback):
177 def make_report(self,traceback):
167 """Return a string containing a crash report."""
178 """Return a string containing a crash report."""
168
179
169 sec_sep = self.section_sep
180 sec_sep = self.section_sep
170
181
171 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
182 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
172 rpt_add = report.append
183 rpt_add = report.append
173 rpt_add(sys_info())
184 rpt_add(sys_info())
174
185
175 try:
186 try:
176 config = pformat(self.app.config)
187 config = pformat(self.app.config)
177 rpt_add(sec_sep)
188 rpt_add(sec_sep)
178 rpt_add('Application name: %s\n\n' % self.app_name)
189 rpt_add('Application name: %s\n\n' % self.app_name)
179 rpt_add('Current user configuration structure:\n\n')
190 rpt_add('Current user configuration structure:\n\n')
180 rpt_add(config)
191 rpt_add(config)
181 except:
192 except:
182 pass
193 pass
183 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
194 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
184
195
185 return ''.join(report)
196 return ''.join(report)
186
197
198
199 def crash_handler_lite(etype, evalue, tb):
200 """a light excepthook, adding a small message to the usual traceback"""
201 traceback.print_exception(etype, evalue, tb)
202
203 from IPython.core.interactiveshell import InteractiveShell
204 if InteractiveShell.initialized():
205 # we are in a Shell environment, give %magic example
206 config = "%config "
207 else:
208 # we are not in a shell, show generic config
209 config = "c."
210 print >> sys.stderr, _lite_message_template.format(email=author_email, config=config)
211
@@ -1,396 +1,397 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2010 The IPython Development Team
16 # Copyright (C) 2008-2010 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader, ConfigFileNotFound
33 Config, PyFileConfigLoader, ConfigFileNotFound
34 )
34 )
35 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.config.application import boolean_flag, catch_config_error
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.completer import IPCompleter
38 from IPython.core.completer import IPCompleter
39 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.application import (
41 from IPython.core.application import (
42 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
43 )
43 )
44 from IPython.core.shellapp import (
44 from IPython.core.shellapp import (
45 InteractiveShellApp, shell_flags, shell_aliases
45 InteractiveShellApp, shell_flags, shell_aliases
46 )
46 )
47 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
48 from IPython.lib import inputhook
48 from IPython.lib import inputhook
49 from IPython.utils import warn
49 from IPython.utils import warn
50 from IPython.utils.path import get_ipython_dir, check_for_old_config
50 from IPython.utils.path import get_ipython_dir, check_for_old_config
51 from IPython.utils.traitlets import (
51 from IPython.utils.traitlets import (
52 Bool, List, Dict, CaselessStrEnum
52 Bool, List, Dict, CaselessStrEnum
53 )
53 )
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Globals, utilities and helpers
56 # Globals, utilities and helpers
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 #: The default config file name for this application.
59 #: The default config file name for this application.
60 default_config_file_name = u'ipython_config.py'
60 default_config_file_name = u'ipython_config.py'
61
61
62 _examples = """
62 _examples = """
63 ipython --pylab # start in pylab mode
63 ipython --pylab # start in pylab mode
64 ipython --pylab=qt # start in pylab mode with the qt4 backend
64 ipython --pylab=qt # start in pylab mode with the qt4 backend
65 ipython --log-level=DEBUG # set logging to DEBUG
65 ipython --log-level=DEBUG # set logging to DEBUG
66 ipython --profile=foo # start with profile foo
66 ipython --profile=foo # start with profile foo
67
67
68 ipython qtconsole # start the qtconsole GUI application
68 ipython qtconsole # start the qtconsole GUI application
69 ipython qtconsole -h # show the help string for the qtconsole subcmd
69 ipython qtconsole -h # show the help string for the qtconsole subcmd
70
70
71 ipython profile create foo # create profile foo w/ default config files
71 ipython profile create foo # create profile foo w/ default config files
72 ipython profile -h # show the help string for the profile subcmd
72 ipython profile -h # show the help string for the profile subcmd
73 """
73 """
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # Crash handler for this application
76 # Crash handler for this application
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79 class IPAppCrashHandler(CrashHandler):
79 class IPAppCrashHandler(CrashHandler):
80 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
80 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
81
81
82 def __init__(self, app):
82 def __init__(self, app):
83 contact_name = release.authors['Fernando'][0]
83 contact_name = release.authors['Fernando'][0]
84 contact_email = release.authors['Fernando'][1]
84 contact_email = release.author_email
85 bug_tracker = 'http://github.com/ipython/ipython/issues'
85 bug_tracker = 'https://github.com/ipython/ipython/issues'
86 super(IPAppCrashHandler,self).__init__(
86 super(IPAppCrashHandler,self).__init__(
87 app, contact_name, contact_email, bug_tracker
87 app, contact_name, contact_email, bug_tracker
88 )
88 )
89
89
90 def make_report(self,traceback):
90 def make_report(self,traceback):
91 """Return a string containing a crash report."""
91 """Return a string containing a crash report."""
92
92
93 sec_sep = self.section_sep
93 sec_sep = self.section_sep
94 # Start with parent report
94 # Start with parent report
95 report = [super(IPAppCrashHandler, self).make_report(traceback)]
95 report = [super(IPAppCrashHandler, self).make_report(traceback)]
96 # Add interactive-specific info we may have
96 # Add interactive-specific info we may have
97 rpt_add = report.append
97 rpt_add = report.append
98 try:
98 try:
99 rpt_add(sec_sep+"History of session input:")
99 rpt_add(sec_sep+"History of session input:")
100 for line in self.app.shell.user_ns['_ih']:
100 for line in self.app.shell.user_ns['_ih']:
101 rpt_add(line)
101 rpt_add(line)
102 rpt_add('\n*** Last line of input (may not be in above history):\n')
102 rpt_add('\n*** Last line of input (may not be in above history):\n')
103 rpt_add(self.app.shell._last_input_line+'\n')
103 rpt_add(self.app.shell._last_input_line+'\n')
104 except:
104 except:
105 pass
105 pass
106
106
107 return ''.join(report)
107 return ''.join(report)
108
108
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110 # Aliases and Flags
110 # Aliases and Flags
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 flags = dict(base_flags)
112 flags = dict(base_flags)
113 flags.update(shell_flags)
113 flags.update(shell_flags)
114 addflag = lambda *args: flags.update(boolean_flag(*args))
114 addflag = lambda *args: flags.update(boolean_flag(*args))
115 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
115 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
116 'Turn on auto editing of files with syntax errors.',
116 'Turn on auto editing of files with syntax errors.',
117 'Turn off auto editing of files with syntax errors.'
117 'Turn off auto editing of files with syntax errors.'
118 )
118 )
119 addflag('banner', 'TerminalIPythonApp.display_banner',
119 addflag('banner', 'TerminalIPythonApp.display_banner',
120 "Display a banner upon starting IPython.",
120 "Display a banner upon starting IPython.",
121 "Don't display a banner upon starting IPython."
121 "Don't display a banner upon starting IPython."
122 )
122 )
123 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
123 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
124 """Set to confirm when you try to exit IPython with an EOF (Control-D
124 """Set to confirm when you try to exit IPython with an EOF (Control-D
125 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
125 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
126 you can force a direct exit without any confirmation.""",
126 you can force a direct exit without any confirmation.""",
127 "Don't prompt the user when exiting."
127 "Don't prompt the user when exiting."
128 )
128 )
129 addflag('term-title', 'TerminalInteractiveShell.term_title',
129 addflag('term-title', 'TerminalInteractiveShell.term_title',
130 "Enable auto setting the terminal title.",
130 "Enable auto setting the terminal title.",
131 "Disable auto setting the terminal title."
131 "Disable auto setting the terminal title."
132 )
132 )
133 classic_config = Config()
133 classic_config = Config()
134 classic_config.InteractiveShell.cache_size = 0
134 classic_config.InteractiveShell.cache_size = 0
135 classic_config.PlainTextFormatter.pprint = False
135 classic_config.PlainTextFormatter.pprint = False
136 classic_config.InteractiveShell.prompt_in1 = '>>> '
136 classic_config.InteractiveShell.prompt_in1 = '>>> '
137 classic_config.InteractiveShell.prompt_in2 = '... '
137 classic_config.InteractiveShell.prompt_in2 = '... '
138 classic_config.InteractiveShell.prompt_out = ''
138 classic_config.InteractiveShell.prompt_out = ''
139 classic_config.InteractiveShell.separate_in = ''
139 classic_config.InteractiveShell.separate_in = ''
140 classic_config.InteractiveShell.separate_out = ''
140 classic_config.InteractiveShell.separate_out = ''
141 classic_config.InteractiveShell.separate_out2 = ''
141 classic_config.InteractiveShell.separate_out2 = ''
142 classic_config.InteractiveShell.colors = 'NoColor'
142 classic_config.InteractiveShell.colors = 'NoColor'
143 classic_config.InteractiveShell.xmode = 'Plain'
143 classic_config.InteractiveShell.xmode = 'Plain'
144
144
145 flags['classic']=(
145 flags['classic']=(
146 classic_config,
146 classic_config,
147 "Gives IPython a similar feel to the classic Python prompt."
147 "Gives IPython a similar feel to the classic Python prompt."
148 )
148 )
149 # # log doesn't make so much sense this way anymore
149 # # log doesn't make so much sense this way anymore
150 # paa('--log','-l',
150 # paa('--log','-l',
151 # action='store_true', dest='InteractiveShell.logstart',
151 # action='store_true', dest='InteractiveShell.logstart',
152 # help="Start logging to the default log file (./ipython_log.py).")
152 # help="Start logging to the default log file (./ipython_log.py).")
153 #
153 #
154 # # quick is harder to implement
154 # # quick is harder to implement
155 flags['quick']=(
155 flags['quick']=(
156 {'TerminalIPythonApp' : {'quick' : True}},
156 {'TerminalIPythonApp' : {'quick' : True}},
157 "Enable quick startup with no config files."
157 "Enable quick startup with no config files."
158 )
158 )
159
159
160 flags['i'] = (
160 flags['i'] = (
161 {'TerminalIPythonApp' : {'force_interact' : True}},
161 {'TerminalIPythonApp' : {'force_interact' : True}},
162 """If running code from the command line, become interactive afterwards.
162 """If running code from the command line, become interactive afterwards.
163 Note: can also be given simply as '-i.'"""
163 Note: can also be given simply as '-i.'"""
164 )
164 )
165 flags['pylab'] = (
165 flags['pylab'] = (
166 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
166 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
167 """Pre-load matplotlib and numpy for interactive use with
167 """Pre-load matplotlib and numpy for interactive use with
168 the default matplotlib backend."""
168 the default matplotlib backend."""
169 )
169 )
170
170
171 aliases = dict(base_aliases)
171 aliases = dict(base_aliases)
172 aliases.update(shell_aliases)
172 aliases.update(shell_aliases)
173
173
174 # it's possible we don't want short aliases for *all* of these:
174 # it's possible we don't want short aliases for *all* of these:
175 aliases.update(dict(
175 aliases.update(dict(
176 gui='TerminalIPythonApp.gui',
176 gui='TerminalIPythonApp.gui',
177 pylab='TerminalIPythonApp.pylab',
177 pylab='TerminalIPythonApp.pylab',
178 ))
178 ))
179
179
180 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
181 # Main classes and functions
181 # Main classes and functions
182 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
183
183
184 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
184 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
185 name = u'ipython'
185 name = u'ipython'
186 description = usage.cl_usage
186 description = usage.cl_usage
187 default_config_file_name = default_config_file_name
187 default_config_file_name = default_config_file_name
188 crash_handler_class = IPAppCrashHandler
188 crash_handler_class = IPAppCrashHandler
189 examples = _examples
189 examples = _examples
190
190
191 flags = Dict(flags)
191 flags = Dict(flags)
192 aliases = Dict(aliases)
192 aliases = Dict(aliases)
193 classes = List()
193 classes = List()
194 def _classes_default(self):
194 def _classes_default(self):
195 """This has to be in a method, for TerminalIPythonApp to be available."""
195 """This has to be in a method, for TerminalIPythonApp to be available."""
196 return [
196 return [
197 InteractiveShellApp, # ShellApp comes before TerminalApp, because
197 InteractiveShellApp, # ShellApp comes before TerminalApp, because
198 self.__class__, # it will also affect subclasses (e.g. QtConsole)
198 self.__class__, # it will also affect subclasses (e.g. QtConsole)
199 TerminalInteractiveShell,
199 TerminalInteractiveShell,
200 ProfileDir,
200 ProfileDir,
201 PlainTextFormatter,
201 PlainTextFormatter,
202 IPCompleter,
202 IPCompleter,
203 ]
203 ]
204
204
205 subcommands = Dict(dict(
205 subcommands = Dict(dict(
206 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
206 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
207 """Launch the IPython Qt Console."""
207 """Launch the IPython Qt Console."""
208 ),
208 ),
209 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
209 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
210 """Launch the IPython HTML Notebook Server"""
210 """Launch the IPython HTML Notebook Server"""
211 ),
211 ),
212 profile = ("IPython.core.profileapp.ProfileApp",
212 profile = ("IPython.core.profileapp.ProfileApp",
213 "Create and manage IPython profiles."
213 "Create and manage IPython profiles."
214 ),
214 ),
215 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
215 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
216 "Start a kernel without an attached frontend."
216 "Start a kernel without an attached frontend."
217 ),
217 ),
218 ))
218 ))
219
219
220 # *do* autocreate requested profile, but don't create the config file.
220 # *do* autocreate requested profile, but don't create the config file.
221 auto_create=Bool(True)
221 auto_create=Bool(True)
222 # configurables
222 # configurables
223 ignore_old_config=Bool(False, config=True,
223 ignore_old_config=Bool(False, config=True,
224 help="Suppress warning messages about legacy config files"
224 help="Suppress warning messages about legacy config files"
225 )
225 )
226 quick = Bool(False, config=True,
226 quick = Bool(False, config=True,
227 help="""Start IPython quickly by skipping the loading of config files."""
227 help="""Start IPython quickly by skipping the loading of config files."""
228 )
228 )
229 def _quick_changed(self, name, old, new):
229 def _quick_changed(self, name, old, new):
230 if new:
230 if new:
231 self.load_config_file = lambda *a, **kw: None
231 self.load_config_file = lambda *a, **kw: None
232 self.ignore_old_config=True
232 self.ignore_old_config=True
233
233
234 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
234 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
235 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
235 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
236 )
236 )
237 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
237 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
238 config=True,
238 config=True,
239 help="""Pre-load matplotlib and numpy for interactive use,
239 help="""Pre-load matplotlib and numpy for interactive use,
240 selecting a particular matplotlib backend and loop integration.
240 selecting a particular matplotlib backend and loop integration.
241 """
241 """
242 )
242 )
243 display_banner = Bool(True, config=True,
243 display_banner = Bool(True, config=True,
244 help="Whether to display a banner upon starting IPython."
244 help="Whether to display a banner upon starting IPython."
245 )
245 )
246
246
247 # if there is code of files to run from the cmd line, don't interact
247 # if there is code of files to run from the cmd line, don't interact
248 # unless the --i flag (App.force_interact) is true.
248 # unless the --i flag (App.force_interact) is true.
249 force_interact = Bool(False, config=True,
249 force_interact = Bool(False, config=True,
250 help="""If a command or file is given via the command-line,
250 help="""If a command or file is given via the command-line,
251 e.g. 'ipython foo.py"""
251 e.g. 'ipython foo.py"""
252 )
252 )
253 def _force_interact_changed(self, name, old, new):
253 def _force_interact_changed(self, name, old, new):
254 if new:
254 if new:
255 self.interact = True
255 self.interact = True
256
256
257 def _file_to_run_changed(self, name, old, new):
257 def _file_to_run_changed(self, name, old, new):
258 if new and not self.force_interact:
258 if new and not self.force_interact:
259 self.interact = False
259 self.interact = False
260 _code_to_run_changed = _file_to_run_changed
260 _code_to_run_changed = _file_to_run_changed
261
261
262 # internal, not-configurable
262 # internal, not-configurable
263 interact=Bool(True)
263 interact=Bool(True)
264
264
265
265
266 def parse_command_line(self, argv=None):
266 def parse_command_line(self, argv=None):
267 """override to allow old '-pylab' flag with deprecation warning"""
267 """override to allow old '-pylab' flag with deprecation warning"""
268
268
269 argv = sys.argv[1:] if argv is None else argv
269 argv = sys.argv[1:] if argv is None else argv
270
270
271 if '-pylab' in argv:
271 if '-pylab' in argv:
272 # deprecated `-pylab` given,
272 # deprecated `-pylab` given,
273 # warn and transform into current syntax
273 # warn and transform into current syntax
274 argv = argv[:] # copy, don't clobber
274 argv = argv[:] # copy, don't clobber
275 idx = argv.index('-pylab')
275 idx = argv.index('-pylab')
276 warn.warn("`-pylab` flag has been deprecated.\n"
276 warn.warn("`-pylab` flag has been deprecated.\n"
277 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
277 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
278 sub = '--pylab'
278 sub = '--pylab'
279 if len(argv) > idx+1:
279 if len(argv) > idx+1:
280 # check for gui arg, as in '-pylab qt'
280 # check for gui arg, as in '-pylab qt'
281 gui = argv[idx+1]
281 gui = argv[idx+1]
282 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
282 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
283 sub = '--pylab='+gui
283 sub = '--pylab='+gui
284 argv.pop(idx+1)
284 argv.pop(idx+1)
285 argv[idx] = sub
285 argv[idx] = sub
286
286
287 return super(TerminalIPythonApp, self).parse_command_line(argv)
287 return super(TerminalIPythonApp, self).parse_command_line(argv)
288
288
289 @catch_config_error
289 @catch_config_error
290 def initialize(self, argv=None):
290 def initialize(self, argv=None):
291 """Do actions after construct, but before starting the app."""
291 """Do actions after construct, but before starting the app."""
292 super(TerminalIPythonApp, self).initialize(argv)
292 super(TerminalIPythonApp, self).initialize(argv)
293 if self.subapp is not None:
293 if self.subapp is not None:
294 # don't bother initializing further, starting subapp
294 # don't bother initializing further, starting subapp
295 return
295 return
296 if not self.ignore_old_config:
296 if not self.ignore_old_config:
297 check_for_old_config(self.ipython_dir)
297 check_for_old_config(self.ipython_dir)
298 # print self.extra_args
298 # print self.extra_args
299 if self.extra_args:
299 if self.extra_args:
300 self.file_to_run = self.extra_args[0]
300 self.file_to_run = self.extra_args[0]
301 # create the shell
301 # create the shell
302 self.init_shell()
302 self.init_shell()
303 # and draw the banner
303 # and draw the banner
304 self.init_banner()
304 self.init_banner()
305 # Now a variety of things that happen after the banner is printed.
305 # Now a variety of things that happen after the banner is printed.
306 self.init_gui_pylab()
306 self.init_gui_pylab()
307 self.init_extensions()
307 self.init_extensions()
308 self.init_code()
308 self.init_code()
309
309
310 def init_shell(self):
310 def init_shell(self):
311 """initialize the InteractiveShell instance"""
311 """initialize the InteractiveShell instance"""
312 # I am a little hesitant to put these into InteractiveShell itself.
312 # I am a little hesitant to put these into InteractiveShell itself.
313 # But that might be the place for them
313 # But that might be the place for them
314 sys.path.insert(0, '')
314 sys.path.insert(0, '')
315
315
316 # Create an InteractiveShell instance.
316 # Create an InteractiveShell instance.
317 # shell.display_banner should always be False for the terminal
317 # shell.display_banner should always be False for the terminal
318 # based app, because we call shell.show_banner() by hand below
318 # based app, because we call shell.show_banner() by hand below
319 # so the banner shows *before* all extension loading stuff.
319 # so the banner shows *before* all extension loading stuff.
320 self.shell = TerminalInteractiveShell.instance(config=self.config,
320 self.shell = TerminalInteractiveShell.instance(config=self.config,
321 display_banner=False, profile_dir=self.profile_dir,
321 display_banner=False, profile_dir=self.profile_dir,
322 ipython_dir=self.ipython_dir)
322 ipython_dir=self.ipython_dir)
323 self.shell.configurables.append(self)
323
324
324 def init_banner(self):
325 def init_banner(self):
325 """optionally display the banner"""
326 """optionally display the banner"""
326 if self.display_banner and self.interact:
327 if self.display_banner and self.interact:
327 self.shell.show_banner()
328 self.shell.show_banner()
328 # Make sure there is a space below the banner.
329 # Make sure there is a space below the banner.
329 if self.log_level <= logging.INFO: print
330 if self.log_level <= logging.INFO: print
330
331
331
332
332 def init_gui_pylab(self):
333 def init_gui_pylab(self):
333 """Enable GUI event loop integration, taking pylab into account."""
334 """Enable GUI event loop integration, taking pylab into account."""
334 gui = self.gui
335 gui = self.gui
335
336
336 # Using `pylab` will also require gui activation, though which toolkit
337 # Using `pylab` will also require gui activation, though which toolkit
337 # to use may be chosen automatically based on mpl configuration.
338 # to use may be chosen automatically based on mpl configuration.
338 if self.pylab:
339 if self.pylab:
339 activate = self.shell.enable_pylab
340 activate = self.shell.enable_pylab
340 if self.pylab == 'auto':
341 if self.pylab == 'auto':
341 gui = None
342 gui = None
342 else:
343 else:
343 gui = self.pylab
344 gui = self.pylab
344 else:
345 else:
345 # Enable only GUI integration, no pylab
346 # Enable only GUI integration, no pylab
346 activate = inputhook.enable_gui
347 activate = inputhook.enable_gui
347
348
348 if gui or self.pylab:
349 if gui or self.pylab:
349 try:
350 try:
350 self.log.info("Enabling GUI event loop integration, "
351 self.log.info("Enabling GUI event loop integration, "
351 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
352 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
352 if self.pylab:
353 if self.pylab:
353 activate(gui, import_all=self.pylab_import_all)
354 activate(gui, import_all=self.pylab_import_all)
354 else:
355 else:
355 activate(gui)
356 activate(gui)
356 except:
357 except:
357 self.log.warn("Error in enabling GUI event loop integration:")
358 self.log.warn("Error in enabling GUI event loop integration:")
358 self.shell.showtraceback()
359 self.shell.showtraceback()
359
360
360 def start(self):
361 def start(self):
361 if self.subapp is not None:
362 if self.subapp is not None:
362 return self.subapp.start()
363 return self.subapp.start()
363 # perform any prexec steps:
364 # perform any prexec steps:
364 if self.interact:
365 if self.interact:
365 self.log.debug("Starting IPython's mainloop...")
366 self.log.debug("Starting IPython's mainloop...")
366 self.shell.mainloop()
367 self.shell.mainloop()
367 else:
368 else:
368 self.log.debug("IPython not interactive...")
369 self.log.debug("IPython not interactive...")
369
370
370
371
371 def load_default_config(ipython_dir=None):
372 def load_default_config(ipython_dir=None):
372 """Load the default config file from the default ipython_dir.
373 """Load the default config file from the default ipython_dir.
373
374
374 This is useful for embedded shells.
375 This is useful for embedded shells.
375 """
376 """
376 if ipython_dir is None:
377 if ipython_dir is None:
377 ipython_dir = get_ipython_dir()
378 ipython_dir = get_ipython_dir()
378 profile_dir = os.path.join(ipython_dir, 'profile_default')
379 profile_dir = os.path.join(ipython_dir, 'profile_default')
379 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
380 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
380 try:
381 try:
381 config = cl.load_config()
382 config = cl.load_config()
382 except ConfigFileNotFound:
383 except ConfigFileNotFound:
383 # no config found
384 # no config found
384 config = Config()
385 config = Config()
385 return config
386 return config
386
387
387
388
388 def launch_new_instance():
389 def launch_new_instance():
389 """Create and run a full blown IPython instance"""
390 """Create and run a full blown IPython instance"""
390 app = TerminalIPythonApp.instance()
391 app = TerminalIPythonApp.instance()
391 app.initialize()
392 app.initialize()
392 app.start()
393 app.start()
393
394
394
395
395 if __name__ == '__main__':
396 if __name__ == '__main__':
396 launch_new_instance()
397 launch_new_instance()
@@ -1,263 +1,263 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 The Base Application class for IPython.parallel apps
3 The Base Application class for IPython.parallel apps
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Min RK
8 * Min RK
9
9
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from __future__ import with_statement
23 from __future__ import with_statement
24
24
25 import os
25 import os
26 import logging
26 import logging
27 import re
27 import re
28 import sys
28 import sys
29
29
30 from subprocess import Popen, PIPE
30 from subprocess import Popen, PIPE
31
31
32 from IPython.config.application import catch_config_error
32 from IPython.config.application import catch_config_error
33 from IPython.core import release
33 from IPython.core import release
34 from IPython.core.crashhandler import CrashHandler
34 from IPython.core.crashhandler import CrashHandler
35 from IPython.core.application import (
35 from IPython.core.application import (
36 BaseIPythonApplication,
36 BaseIPythonApplication,
37 base_aliases as base_ip_aliases,
37 base_aliases as base_ip_aliases,
38 base_flags as base_ip_flags
38 base_flags as base_ip_flags
39 )
39 )
40 from IPython.utils.path import expand_path
40 from IPython.utils.path import expand_path
41
41
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Module errors
45 # Module errors
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class PIDFileError(Exception):
48 class PIDFileError(Exception):
49 pass
49 pass
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Crash handler for this application
53 # Crash handler for this application
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 class ParallelCrashHandler(CrashHandler):
56 class ParallelCrashHandler(CrashHandler):
57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
58
58
59 def __init__(self, app):
59 def __init__(self, app):
60 contact_name = release.authors['Min'][0]
60 contact_name = release.authors['Min'][0]
61 contact_email = release.authors['Min'][1]
61 contact_email = release.author_email
62 bug_tracker = 'http://github.com/ipython/ipython/issues'
62 bug_tracker = 'https://github.com/ipython/ipython/issues'
63 super(ParallelCrashHandler,self).__init__(
63 super(ParallelCrashHandler,self).__init__(
64 app, contact_name, contact_email, bug_tracker
64 app, contact_name, contact_email, bug_tracker
65 )
65 )
66
66
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Main application
69 # Main application
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 base_aliases = {}
71 base_aliases = {}
72 base_aliases.update(base_ip_aliases)
72 base_aliases.update(base_ip_aliases)
73 base_aliases.update({
73 base_aliases.update({
74 'profile-dir' : 'ProfileDir.location',
74 'profile-dir' : 'ProfileDir.location',
75 'work-dir' : 'BaseParallelApplication.work_dir',
75 'work-dir' : 'BaseParallelApplication.work_dir',
76 'log-to-file' : 'BaseParallelApplication.log_to_file',
76 'log-to-file' : 'BaseParallelApplication.log_to_file',
77 'clean-logs' : 'BaseParallelApplication.clean_logs',
77 'clean-logs' : 'BaseParallelApplication.clean_logs',
78 'log-url' : 'BaseParallelApplication.log_url',
78 'log-url' : 'BaseParallelApplication.log_url',
79 'cluster-id' : 'BaseParallelApplication.cluster_id',
79 'cluster-id' : 'BaseParallelApplication.cluster_id',
80 })
80 })
81
81
82 base_flags = {
82 base_flags = {
83 'log-to-file' : (
83 'log-to-file' : (
84 {'BaseParallelApplication' : {'log_to_file' : True}},
84 {'BaseParallelApplication' : {'log_to_file' : True}},
85 "send log output to a file"
85 "send log output to a file"
86 )
86 )
87 }
87 }
88 base_flags.update(base_ip_flags)
88 base_flags.update(base_ip_flags)
89
89
90 class BaseParallelApplication(BaseIPythonApplication):
90 class BaseParallelApplication(BaseIPythonApplication):
91 """The base Application for IPython.parallel apps
91 """The base Application for IPython.parallel apps
92
92
93 Principle extensions to BaseIPyythonApplication:
93 Principle extensions to BaseIPyythonApplication:
94
94
95 * work_dir
95 * work_dir
96 * remote logging via pyzmq
96 * remote logging via pyzmq
97 * IOLoop instance
97 * IOLoop instance
98 """
98 """
99
99
100 crash_handler_class = ParallelCrashHandler
100 crash_handler_class = ParallelCrashHandler
101
101
102 def _log_level_default(self):
102 def _log_level_default(self):
103 # temporarily override default_log_level to INFO
103 # temporarily override default_log_level to INFO
104 return logging.INFO
104 return logging.INFO
105
105
106 work_dir = Unicode(os.getcwdu(), config=True,
106 work_dir = Unicode(os.getcwdu(), config=True,
107 help='Set the working dir for the process.'
107 help='Set the working dir for the process.'
108 )
108 )
109 def _work_dir_changed(self, name, old, new):
109 def _work_dir_changed(self, name, old, new):
110 self.work_dir = unicode(expand_path(new))
110 self.work_dir = unicode(expand_path(new))
111
111
112 log_to_file = Bool(config=True,
112 log_to_file = Bool(config=True,
113 help="whether to log to a file")
113 help="whether to log to a file")
114
114
115 clean_logs = Bool(False, config=True,
115 clean_logs = Bool(False, config=True,
116 help="whether to cleanup old logfiles before starting")
116 help="whether to cleanup old logfiles before starting")
117
117
118 log_url = Unicode('', config=True,
118 log_url = Unicode('', config=True,
119 help="The ZMQ URL of the iplogger to aggregate logging.")
119 help="The ZMQ URL of the iplogger to aggregate logging.")
120
120
121 cluster_id = Unicode('', config=True,
121 cluster_id = Unicode('', config=True,
122 help="""String id to add to runtime files, to prevent name collisions when
122 help="""String id to add to runtime files, to prevent name collisions when
123 using multiple clusters with a single profile simultaneously.
123 using multiple clusters with a single profile simultaneously.
124
124
125 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
125 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
126
126
127 Since this is text inserted into filenames, typical recommendations apply:
127 Since this is text inserted into filenames, typical recommendations apply:
128 Simple character strings are ideal, and spaces are not recommended (but should
128 Simple character strings are ideal, and spaces are not recommended (but should
129 generally work).
129 generally work).
130 """
130 """
131 )
131 )
132 def _cluster_id_changed(self, name, old, new):
132 def _cluster_id_changed(self, name, old, new):
133 self.name = self.__class__.name
133 self.name = self.__class__.name
134 if new:
134 if new:
135 self.name += '-%s'%new
135 self.name += '-%s'%new
136
136
137 def _config_files_default(self):
137 def _config_files_default(self):
138 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
138 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
139
139
140 loop = Instance('zmq.eventloop.ioloop.IOLoop')
140 loop = Instance('zmq.eventloop.ioloop.IOLoop')
141 def _loop_default(self):
141 def _loop_default(self):
142 from zmq.eventloop.ioloop import IOLoop
142 from zmq.eventloop.ioloop import IOLoop
143 return IOLoop.instance()
143 return IOLoop.instance()
144
144
145 aliases = Dict(base_aliases)
145 aliases = Dict(base_aliases)
146 flags = Dict(base_flags)
146 flags = Dict(base_flags)
147
147
148 @catch_config_error
148 @catch_config_error
149 def initialize(self, argv=None):
149 def initialize(self, argv=None):
150 """initialize the app"""
150 """initialize the app"""
151 super(BaseParallelApplication, self).initialize(argv)
151 super(BaseParallelApplication, self).initialize(argv)
152 self.to_work_dir()
152 self.to_work_dir()
153 self.reinit_logging()
153 self.reinit_logging()
154
154
155 def to_work_dir(self):
155 def to_work_dir(self):
156 wd = self.work_dir
156 wd = self.work_dir
157 if unicode(wd) != os.getcwdu():
157 if unicode(wd) != os.getcwdu():
158 os.chdir(wd)
158 os.chdir(wd)
159 self.log.info("Changing to working dir: %s" % wd)
159 self.log.info("Changing to working dir: %s" % wd)
160 # This is the working dir by now.
160 # This is the working dir by now.
161 sys.path.insert(0, '')
161 sys.path.insert(0, '')
162
162
163 def reinit_logging(self):
163 def reinit_logging(self):
164 # Remove old log files
164 # Remove old log files
165 log_dir = self.profile_dir.log_dir
165 log_dir = self.profile_dir.log_dir
166 if self.clean_logs:
166 if self.clean_logs:
167 for f in os.listdir(log_dir):
167 for f in os.listdir(log_dir):
168 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
168 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
169 os.remove(os.path.join(log_dir, f))
169 os.remove(os.path.join(log_dir, f))
170 if self.log_to_file:
170 if self.log_to_file:
171 # Start logging to the new log file
171 # Start logging to the new log file
172 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
172 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
173 logfile = os.path.join(log_dir, log_filename)
173 logfile = os.path.join(log_dir, log_filename)
174 open_log_file = open(logfile, 'w')
174 open_log_file = open(logfile, 'w')
175 else:
175 else:
176 open_log_file = None
176 open_log_file = None
177 if open_log_file is not None:
177 if open_log_file is not None:
178 self.log.removeHandler(self._log_handler)
178 self.log.removeHandler(self._log_handler)
179 self._log_handler = logging.StreamHandler(open_log_file)
179 self._log_handler = logging.StreamHandler(open_log_file)
180 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
180 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
181 self._log_handler.setFormatter(self._log_formatter)
181 self._log_handler.setFormatter(self._log_formatter)
182 self.log.addHandler(self._log_handler)
182 self.log.addHandler(self._log_handler)
183 # do not propagate log messages to root logger
183 # do not propagate log messages to root logger
184 # ipcluster app will sometimes print duplicate messages during shutdown
184 # ipcluster app will sometimes print duplicate messages during shutdown
185 # if this is 1 (default):
185 # if this is 1 (default):
186 self.log.propagate = False
186 self.log.propagate = False
187
187
188 def write_pid_file(self, overwrite=False):
188 def write_pid_file(self, overwrite=False):
189 """Create a .pid file in the pid_dir with my pid.
189 """Create a .pid file in the pid_dir with my pid.
190
190
191 This must be called after pre_construct, which sets `self.pid_dir`.
191 This must be called after pre_construct, which sets `self.pid_dir`.
192 This raises :exc:`PIDFileError` if the pid file exists already.
192 This raises :exc:`PIDFileError` if the pid file exists already.
193 """
193 """
194 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
194 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
195 if os.path.isfile(pid_file):
195 if os.path.isfile(pid_file):
196 pid = self.get_pid_from_file()
196 pid = self.get_pid_from_file()
197 if not overwrite:
197 if not overwrite:
198 raise PIDFileError(
198 raise PIDFileError(
199 'The pid file [%s] already exists. \nThis could mean that this '
199 'The pid file [%s] already exists. \nThis could mean that this '
200 'server is already running with [pid=%s].' % (pid_file, pid)
200 'server is already running with [pid=%s].' % (pid_file, pid)
201 )
201 )
202 with open(pid_file, 'w') as f:
202 with open(pid_file, 'w') as f:
203 self.log.info("Creating pid file: %s" % pid_file)
203 self.log.info("Creating pid file: %s" % pid_file)
204 f.write(repr(os.getpid())+'\n')
204 f.write(repr(os.getpid())+'\n')
205
205
206 def remove_pid_file(self):
206 def remove_pid_file(self):
207 """Remove the pid file.
207 """Remove the pid file.
208
208
209 This should be called at shutdown by registering a callback with
209 This should be called at shutdown by registering a callback with
210 :func:`reactor.addSystemEventTrigger`. This needs to return
210 :func:`reactor.addSystemEventTrigger`. This needs to return
211 ``None``.
211 ``None``.
212 """
212 """
213 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
213 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
214 if os.path.isfile(pid_file):
214 if os.path.isfile(pid_file):
215 try:
215 try:
216 self.log.info("Removing pid file: %s" % pid_file)
216 self.log.info("Removing pid file: %s" % pid_file)
217 os.remove(pid_file)
217 os.remove(pid_file)
218 except:
218 except:
219 self.log.warn("Error removing the pid file: %s" % pid_file)
219 self.log.warn("Error removing the pid file: %s" % pid_file)
220
220
221 def get_pid_from_file(self):
221 def get_pid_from_file(self):
222 """Get the pid from the pid file.
222 """Get the pid from the pid file.
223
223
224 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
224 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
225 """
225 """
226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
227 if os.path.isfile(pid_file):
227 if os.path.isfile(pid_file):
228 with open(pid_file, 'r') as f:
228 with open(pid_file, 'r') as f:
229 s = f.read().strip()
229 s = f.read().strip()
230 try:
230 try:
231 pid = int(s)
231 pid = int(s)
232 except:
232 except:
233 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
233 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
234 return pid
234 return pid
235 else:
235 else:
236 raise PIDFileError('pid file not found: %s' % pid_file)
236 raise PIDFileError('pid file not found: %s' % pid_file)
237
237
238 def check_pid(self, pid):
238 def check_pid(self, pid):
239 if os.name == 'nt':
239 if os.name == 'nt':
240 try:
240 try:
241 import ctypes
241 import ctypes
242 # returns 0 if no such process (of ours) exists
242 # returns 0 if no such process (of ours) exists
243 # positive int otherwise
243 # positive int otherwise
244 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
244 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
245 except Exception:
245 except Exception:
246 self.log.warn(
246 self.log.warn(
247 "Could not determine whether pid %i is running via `OpenProcess`. "
247 "Could not determine whether pid %i is running via `OpenProcess`. "
248 " Making the likely assumption that it is."%pid
248 " Making the likely assumption that it is."%pid
249 )
249 )
250 return True
250 return True
251 return bool(p)
251 return bool(p)
252 else:
252 else:
253 try:
253 try:
254 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
254 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
255 output,_ = p.communicate()
255 output,_ = p.communicate()
256 except OSError:
256 except OSError:
257 self.log.warn(
257 self.log.warn(
258 "Could not determine whether pid %i is running via `ps x`. "
258 "Could not determine whether pid %i is running via `ps x`. "
259 " Making the likely assumption that it is."%pid
259 " Making the likely assumption that it is."%pid
260 )
260 )
261 return True
261 return True
262 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
262 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
263 return pid in pids
263 return pid in pids
@@ -1,813 +1,814 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24 import logging
24 import logging
25
25
26 # System library imports.
26 # System library imports.
27 import zmq
27 import zmq
28
28
29 # Local imports.
29 # Local imports.
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from IPython.config.application import boolean_flag, catch_config_error
31 from IPython.config.application import boolean_flag, catch_config_error
32 from IPython.core.application import ProfileDir
32 from IPython.core.application import ProfileDir
33 from IPython.core.error import StdinNotImplementedError
33 from IPython.core.error import StdinNotImplementedError
34 from IPython.core.shellapp import (
34 from IPython.core.shellapp import (
35 InteractiveShellApp, shell_flags, shell_aliases
35 InteractiveShellApp, shell_flags, shell_aliases
36 )
36 )
37 from IPython.utils import io
37 from IPython.utils import io
38 from IPython.utils import py3compat
38 from IPython.utils import py3compat
39 from IPython.utils.jsonutil import json_clean
39 from IPython.utils.jsonutil import json_clean
40 from IPython.lib import pylabtools
40 from IPython.lib import pylabtools
41 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
42 Any, List, Instance, Float, Dict, Bool, Unicode, CaselessStrEnum
42 Any, List, Instance, Float, Dict, Bool, Unicode, CaselessStrEnum
43 )
43 )
44
44
45 from entry_point import base_launch_kernel
45 from entry_point import base_launch_kernel
46 from kernelapp import KernelApp, kernel_flags, kernel_aliases
46 from kernelapp import KernelApp, kernel_flags, kernel_aliases
47 from iostream import OutStream
47 from iostream import OutStream
48 from session import Session, Message
48 from session import Session, Message
49 from zmqshell import ZMQInteractiveShell
49 from zmqshell import ZMQInteractiveShell
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Main kernel class
53 # Main kernel class
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 class Kernel(Configurable):
56 class Kernel(Configurable):
57
57
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59 # Kernel interface
59 # Kernel interface
60 #---------------------------------------------------------------------------
60 #---------------------------------------------------------------------------
61
61
62 # attribute to override with a GUI
62 # attribute to override with a GUI
63 eventloop = Any(None)
63 eventloop = Any(None)
64
64
65 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
65 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
66 session = Instance(Session)
66 session = Instance(Session)
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
68 shell_socket = Instance('zmq.Socket')
68 shell_socket = Instance('zmq.Socket')
69 iopub_socket = Instance('zmq.Socket')
69 iopub_socket = Instance('zmq.Socket')
70 stdin_socket = Instance('zmq.Socket')
70 stdin_socket = Instance('zmq.Socket')
71 log = Instance(logging.Logger)
71 log = Instance(logging.Logger)
72
72
73 # Private interface
73 # Private interface
74
74
75 # Time to sleep after flushing the stdout/err buffers in each execute
75 # Time to sleep after flushing the stdout/err buffers in each execute
76 # cycle. While this introduces a hard limit on the minimal latency of the
76 # cycle. While this introduces a hard limit on the minimal latency of the
77 # execute cycle, it helps prevent output synchronization problems for
77 # execute cycle, it helps prevent output synchronization problems for
78 # clients.
78 # clients.
79 # Units are in seconds. The minimum zmq latency on local host is probably
79 # Units are in seconds. The minimum zmq latency on local host is probably
80 # ~150 microseconds, set this to 500us for now. We may need to increase it
80 # ~150 microseconds, set this to 500us for now. We may need to increase it
81 # a little if it's not enough after more interactive testing.
81 # a little if it's not enough after more interactive testing.
82 _execute_sleep = Float(0.0005, config=True)
82 _execute_sleep = Float(0.0005, config=True)
83
83
84 # Frequency of the kernel's event loop.
84 # Frequency of the kernel's event loop.
85 # Units are in seconds, kernel subclasses for GUI toolkits may need to
85 # Units are in seconds, kernel subclasses for GUI toolkits may need to
86 # adapt to milliseconds.
86 # adapt to milliseconds.
87 _poll_interval = Float(0.05, config=True)
87 _poll_interval = Float(0.05, config=True)
88
88
89 # If the shutdown was requested over the network, we leave here the
89 # If the shutdown was requested over the network, we leave here the
90 # necessary reply message so it can be sent by our registered atexit
90 # necessary reply message so it can be sent by our registered atexit
91 # handler. This ensures that the reply is only sent to clients truly at
91 # handler. This ensures that the reply is only sent to clients truly at
92 # the end of our shutdown process (which happens after the underlying
92 # the end of our shutdown process (which happens after the underlying
93 # IPython shell's own shutdown).
93 # IPython shell's own shutdown).
94 _shutdown_message = None
94 _shutdown_message = None
95
95
96 # This is a dict of port number that the kernel is listening on. It is set
96 # This is a dict of port number that the kernel is listening on. It is set
97 # by record_ports and used by connect_request.
97 # by record_ports and used by connect_request.
98 _recorded_ports = Dict()
98 _recorded_ports = Dict()
99
99
100
100
101
101
102 def __init__(self, **kwargs):
102 def __init__(self, **kwargs):
103 super(Kernel, self).__init__(**kwargs)
103 super(Kernel, self).__init__(**kwargs)
104
104
105 # Before we even start up the shell, register *first* our exit handlers
105 # Before we even start up the shell, register *first* our exit handlers
106 # so they come before the shell's
106 # so they come before the shell's
107 atexit.register(self._at_shutdown)
107 atexit.register(self._at_shutdown)
108
108
109 # Initialize the InteractiveShell subclass
109 # Initialize the InteractiveShell subclass
110 self.shell = ZMQInteractiveShell.instance(config=self.config,
110 self.shell = ZMQInteractiveShell.instance(config=self.config,
111 profile_dir = self.profile_dir,
111 profile_dir = self.profile_dir,
112 )
112 )
113 self.shell.displayhook.session = self.session
113 self.shell.displayhook.session = self.session
114 self.shell.displayhook.pub_socket = self.iopub_socket
114 self.shell.displayhook.pub_socket = self.iopub_socket
115 self.shell.display_pub.session = self.session
115 self.shell.display_pub.session = self.session
116 self.shell.display_pub.pub_socket = self.iopub_socket
116 self.shell.display_pub.pub_socket = self.iopub_socket
117
117
118 # TMP - hack while developing
118 # TMP - hack while developing
119 self.shell._reply_content = None
119 self.shell._reply_content = None
120
120
121 # Build dict of handlers for message types
121 # Build dict of handlers for message types
122 msg_types = [ 'execute_request', 'complete_request',
122 msg_types = [ 'execute_request', 'complete_request',
123 'object_info_request', 'history_request',
123 'object_info_request', 'history_request',
124 'connect_request', 'shutdown_request']
124 'connect_request', 'shutdown_request']
125 self.handlers = {}
125 self.handlers = {}
126 for msg_type in msg_types:
126 for msg_type in msg_types:
127 self.handlers[msg_type] = getattr(self, msg_type)
127 self.handlers[msg_type] = getattr(self, msg_type)
128
128
129 def do_one_iteration(self):
129 def do_one_iteration(self):
130 """Do one iteration of the kernel's evaluation loop.
130 """Do one iteration of the kernel's evaluation loop.
131 """
131 """
132 try:
132 try:
133 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
133 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
134 except Exception:
134 except Exception:
135 self.log.warn("Invalid Message:", exc_info=True)
135 self.log.warn("Invalid Message:", exc_info=True)
136 return
136 return
137 if msg is None:
137 if msg is None:
138 return
138 return
139
139
140 msg_type = msg['header']['msg_type']
140 msg_type = msg['header']['msg_type']
141
141
142 # This assert will raise in versions of zeromq 2.0.7 and lesser.
142 # This assert will raise in versions of zeromq 2.0.7 and lesser.
143 # We now require 2.0.8 or above, so we can uncomment for safety.
143 # We now require 2.0.8 or above, so we can uncomment for safety.
144 # print(ident,msg, file=sys.__stdout__)
144 # print(ident,msg, file=sys.__stdout__)
145 assert ident is not None, "Missing message part."
145 assert ident is not None, "Missing message part."
146
146
147 # Print some info about this message and leave a '--->' marker, so it's
147 # Print some info about this message and leave a '--->' marker, so it's
148 # easier to trace visually the message chain when debugging. Each
148 # easier to trace visually the message chain when debugging. Each
149 # handler prints its message at the end.
149 # handler prints its message at the end.
150 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
150 self.log.debug('\n*** MESSAGE TYPE:'+str(msg_type)+'***')
151 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
151 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
152
152
153 # Find and call actual handler for message
153 # Find and call actual handler for message
154 handler = self.handlers.get(msg_type, None)
154 handler = self.handlers.get(msg_type, None)
155 if handler is None:
155 if handler is None:
156 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
156 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
157 else:
157 else:
158 handler(ident, msg)
158 handler(ident, msg)
159
159
160 # Check whether we should exit, in case the incoming message set the
160 # Check whether we should exit, in case the incoming message set the
161 # exit flag on
161 # exit flag on
162 if self.shell.exit_now:
162 if self.shell.exit_now:
163 self.log.debug('\nExiting IPython kernel...')
163 self.log.debug('\nExiting IPython kernel...')
164 # We do a normal, clean exit, which allows any actions registered
164 # We do a normal, clean exit, which allows any actions registered
165 # via atexit (such as history saving) to take place.
165 # via atexit (such as history saving) to take place.
166 sys.exit(0)
166 sys.exit(0)
167
167
168
168
169 def start(self):
169 def start(self):
170 """ Start the kernel main loop.
170 """ Start the kernel main loop.
171 """
171 """
172 poller = zmq.Poller()
172 poller = zmq.Poller()
173 poller.register(self.shell_socket, zmq.POLLIN)
173 poller.register(self.shell_socket, zmq.POLLIN)
174 # loop while self.eventloop has not been overridden
174 # loop while self.eventloop has not been overridden
175 while self.eventloop is None:
175 while self.eventloop is None:
176 try:
176 try:
177 # scale by extra factor of 10, because there is no
177 # scale by extra factor of 10, because there is no
178 # reason for this to be anything less than ~ 0.1s
178 # reason for this to be anything less than ~ 0.1s
179 # since it is a real poller and will respond
179 # since it is a real poller and will respond
180 # to events immediately
180 # to events immediately
181
181
182 # double nested try/except, to properly catch KeyboardInterrupt
182 # double nested try/except, to properly catch KeyboardInterrupt
183 # due to pyzmq Issue #130
183 # due to pyzmq Issue #130
184 try:
184 try:
185 poller.poll(10*1000*self._poll_interval)
185 poller.poll(10*1000*self._poll_interval)
186 self.do_one_iteration()
186 self.do_one_iteration()
187 except:
187 except:
188 raise
188 raise
189 except KeyboardInterrupt:
189 except KeyboardInterrupt:
190 # Ctrl-C shouldn't crash the kernel
190 # Ctrl-C shouldn't crash the kernel
191 io.raw_print("KeyboardInterrupt caught in kernel")
191 io.raw_print("KeyboardInterrupt caught in kernel")
192 if self.eventloop is not None:
192 if self.eventloop is not None:
193 try:
193 try:
194 self.eventloop(self)
194 self.eventloop(self)
195 except KeyboardInterrupt:
195 except KeyboardInterrupt:
196 # Ctrl-C shouldn't crash the kernel
196 # Ctrl-C shouldn't crash the kernel
197 io.raw_print("KeyboardInterrupt caught in kernel")
197 io.raw_print("KeyboardInterrupt caught in kernel")
198
198
199
199
200 def record_ports(self, ports):
200 def record_ports(self, ports):
201 """Record the ports that this kernel is using.
201 """Record the ports that this kernel is using.
202
202
203 The creator of the Kernel instance must call this methods if they
203 The creator of the Kernel instance must call this methods if they
204 want the :meth:`connect_request` method to return the port numbers.
204 want the :meth:`connect_request` method to return the port numbers.
205 """
205 """
206 self._recorded_ports = ports
206 self._recorded_ports = ports
207
207
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209 # Kernel request handlers
209 # Kernel request handlers
210 #---------------------------------------------------------------------------
210 #---------------------------------------------------------------------------
211
211
212 def _publish_pyin(self, code, parent):
212 def _publish_pyin(self, code, parent):
213 """Publish the code request on the pyin stream."""
213 """Publish the code request on the pyin stream."""
214
214
215 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
215 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
216
216
217 def execute_request(self, ident, parent):
217 def execute_request(self, ident, parent):
218
218
219 status_msg = self.session.send(self.iopub_socket,
219 status_msg = self.session.send(self.iopub_socket,
220 u'status',
220 u'status',
221 {u'execution_state':u'busy'},
221 {u'execution_state':u'busy'},
222 parent=parent
222 parent=parent
223 )
223 )
224
224
225 try:
225 try:
226 content = parent[u'content']
226 content = parent[u'content']
227 code = content[u'code']
227 code = content[u'code']
228 silent = content[u'silent']
228 silent = content[u'silent']
229 except:
229 except:
230 self.log.error("Got bad msg: ")
230 self.log.error("Got bad msg: ")
231 self.log.error(str(Message(parent)))
231 self.log.error(str(Message(parent)))
232 return
232 return
233
233
234 shell = self.shell # we'll need this a lot here
234 shell = self.shell # we'll need this a lot here
235
235
236 # Replace raw_input. Note that is not sufficient to replace
236 # Replace raw_input. Note that is not sufficient to replace
237 # raw_input in the user namespace.
237 # raw_input in the user namespace.
238 if content.get('allow_stdin', False):
238 if content.get('allow_stdin', False):
239 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
239 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
240 else:
240 else:
241 raw_input = lambda prompt='' : self._no_raw_input()
241 raw_input = lambda prompt='' : self._no_raw_input()
242
242
243 if py3compat.PY3:
243 if py3compat.PY3:
244 __builtin__.input = raw_input
244 __builtin__.input = raw_input
245 else:
245 else:
246 __builtin__.raw_input = raw_input
246 __builtin__.raw_input = raw_input
247
247
248 # Set the parent message of the display hook and out streams.
248 # Set the parent message of the display hook and out streams.
249 shell.displayhook.set_parent(parent)
249 shell.displayhook.set_parent(parent)
250 shell.display_pub.set_parent(parent)
250 shell.display_pub.set_parent(parent)
251 sys.stdout.set_parent(parent)
251 sys.stdout.set_parent(parent)
252 sys.stderr.set_parent(parent)
252 sys.stderr.set_parent(parent)
253
253
254 # Re-broadcast our input for the benefit of listening clients, and
254 # Re-broadcast our input for the benefit of listening clients, and
255 # start computing output
255 # start computing output
256 if not silent:
256 if not silent:
257 self._publish_pyin(code, parent)
257 self._publish_pyin(code, parent)
258
258
259 reply_content = {}
259 reply_content = {}
260 try:
260 try:
261 if silent:
261 if silent:
262 # run_code uses 'exec' mode, so no displayhook will fire, and it
262 # run_code uses 'exec' mode, so no displayhook will fire, and it
263 # doesn't call logging or history manipulations. Print
263 # doesn't call logging or history manipulations. Print
264 # statements in that code will obviously still execute.
264 # statements in that code will obviously still execute.
265 shell.run_code(code)
265 shell.run_code(code)
266 else:
266 else:
267 # FIXME: the shell calls the exception handler itself.
267 # FIXME: the shell calls the exception handler itself.
268 shell.run_cell(code, store_history=True)
268 shell.run_cell(code, store_history=True)
269 except:
269 except:
270 status = u'error'
270 status = u'error'
271 # FIXME: this code right now isn't being used yet by default,
271 # FIXME: this code right now isn't being used yet by default,
272 # because the run_cell() call above directly fires off exception
272 # because the run_cell() call above directly fires off exception
273 # reporting. This code, therefore, is only active in the scenario
273 # reporting. This code, therefore, is only active in the scenario
274 # where runlines itself has an unhandled exception. We need to
274 # where runlines itself has an unhandled exception. We need to
275 # uniformize this, for all exception construction to come from a
275 # uniformize this, for all exception construction to come from a
276 # single location in the codbase.
276 # single location in the codbase.
277 etype, evalue, tb = sys.exc_info()
277 etype, evalue, tb = sys.exc_info()
278 tb_list = traceback.format_exception(etype, evalue, tb)
278 tb_list = traceback.format_exception(etype, evalue, tb)
279 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
279 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
280 else:
280 else:
281 status = u'ok'
281 status = u'ok'
282
282
283 reply_content[u'status'] = status
283 reply_content[u'status'] = status
284
284
285 # Return the execution counter so clients can display prompts
285 # Return the execution counter so clients can display prompts
286 reply_content['execution_count'] = shell.execution_count -1
286 reply_content['execution_count'] = shell.execution_count -1
287
287
288 # FIXME - fish exception info out of shell, possibly left there by
288 # FIXME - fish exception info out of shell, possibly left there by
289 # runlines. We'll need to clean up this logic later.
289 # runlines. We'll need to clean up this logic later.
290 if shell._reply_content is not None:
290 if shell._reply_content is not None:
291 reply_content.update(shell._reply_content)
291 reply_content.update(shell._reply_content)
292 # reset after use
292 # reset after use
293 shell._reply_content = None
293 shell._reply_content = None
294
294
295 # At this point, we can tell whether the main code execution succeeded
295 # At this point, we can tell whether the main code execution succeeded
296 # or not. If it did, we proceed to evaluate user_variables/expressions
296 # or not. If it did, we proceed to evaluate user_variables/expressions
297 if reply_content['status'] == 'ok':
297 if reply_content['status'] == 'ok':
298 reply_content[u'user_variables'] = \
298 reply_content[u'user_variables'] = \
299 shell.user_variables(content[u'user_variables'])
299 shell.user_variables(content[u'user_variables'])
300 reply_content[u'user_expressions'] = \
300 reply_content[u'user_expressions'] = \
301 shell.user_expressions(content[u'user_expressions'])
301 shell.user_expressions(content[u'user_expressions'])
302 else:
302 else:
303 # If there was an error, don't even try to compute variables or
303 # If there was an error, don't even try to compute variables or
304 # expressions
304 # expressions
305 reply_content[u'user_variables'] = {}
305 reply_content[u'user_variables'] = {}
306 reply_content[u'user_expressions'] = {}
306 reply_content[u'user_expressions'] = {}
307
307
308 # Payloads should be retrieved regardless of outcome, so we can both
308 # Payloads should be retrieved regardless of outcome, so we can both
309 # recover partial output (that could have been generated early in a
309 # recover partial output (that could have been generated early in a
310 # block, before an error) and clear the payload system always.
310 # block, before an error) and clear the payload system always.
311 reply_content[u'payload'] = shell.payload_manager.read_payload()
311 reply_content[u'payload'] = shell.payload_manager.read_payload()
312 # Be agressive about clearing the payload because we don't want
312 # Be agressive about clearing the payload because we don't want
313 # it to sit in memory until the next execute_request comes in.
313 # it to sit in memory until the next execute_request comes in.
314 shell.payload_manager.clear_payload()
314 shell.payload_manager.clear_payload()
315
315
316 # Flush output before sending the reply.
316 # Flush output before sending the reply.
317 sys.stdout.flush()
317 sys.stdout.flush()
318 sys.stderr.flush()
318 sys.stderr.flush()
319 # FIXME: on rare occasions, the flush doesn't seem to make it to the
319 # FIXME: on rare occasions, the flush doesn't seem to make it to the
320 # clients... This seems to mitigate the problem, but we definitely need
320 # clients... This seems to mitigate the problem, but we definitely need
321 # to better understand what's going on.
321 # to better understand what's going on.
322 if self._execute_sleep:
322 if self._execute_sleep:
323 time.sleep(self._execute_sleep)
323 time.sleep(self._execute_sleep)
324
324
325 # Send the reply.
325 # Send the reply.
326 reply_content = json_clean(reply_content)
326 reply_content = json_clean(reply_content)
327 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
327 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
328 reply_content, parent, ident=ident)
328 reply_content, parent, ident=ident)
329 self.log.debug(str(reply_msg))
329 self.log.debug(str(reply_msg))
330
330
331 if reply_msg['content']['status'] == u'error':
331 if reply_msg['content']['status'] == u'error':
332 self._abort_queue()
332 self._abort_queue()
333
333
334 status_msg = self.session.send(self.iopub_socket,
334 status_msg = self.session.send(self.iopub_socket,
335 u'status',
335 u'status',
336 {u'execution_state':u'idle'},
336 {u'execution_state':u'idle'},
337 parent=parent
337 parent=parent
338 )
338 )
339
339
340 def complete_request(self, ident, parent):
340 def complete_request(self, ident, parent):
341 txt, matches = self._complete(parent)
341 txt, matches = self._complete(parent)
342 matches = {'matches' : matches,
342 matches = {'matches' : matches,
343 'matched_text' : txt,
343 'matched_text' : txt,
344 'status' : 'ok'}
344 'status' : 'ok'}
345 matches = json_clean(matches)
345 matches = json_clean(matches)
346 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
346 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
347 matches, parent, ident)
347 matches, parent, ident)
348 self.log.debug(str(completion_msg))
348 self.log.debug(str(completion_msg))
349
349
350 def object_info_request(self, ident, parent):
350 def object_info_request(self, ident, parent):
351 object_info = self.shell.object_inspect(parent['content']['oname'])
351 object_info = self.shell.object_inspect(parent['content']['oname'])
352 # Before we send this object over, we scrub it for JSON usage
352 # Before we send this object over, we scrub it for JSON usage
353 oinfo = json_clean(object_info)
353 oinfo = json_clean(object_info)
354 msg = self.session.send(self.shell_socket, 'object_info_reply',
354 msg = self.session.send(self.shell_socket, 'object_info_reply',
355 oinfo, parent, ident)
355 oinfo, parent, ident)
356 self.log.debug(msg)
356 self.log.debug(msg)
357
357
358 def history_request(self, ident, parent):
358 def history_request(self, ident, parent):
359 # We need to pull these out, as passing **kwargs doesn't work with
359 # We need to pull these out, as passing **kwargs doesn't work with
360 # unicode keys before Python 2.6.5.
360 # unicode keys before Python 2.6.5.
361 hist_access_type = parent['content']['hist_access_type']
361 hist_access_type = parent['content']['hist_access_type']
362 raw = parent['content']['raw']
362 raw = parent['content']['raw']
363 output = parent['content']['output']
363 output = parent['content']['output']
364 if hist_access_type == 'tail':
364 if hist_access_type == 'tail':
365 n = parent['content']['n']
365 n = parent['content']['n']
366 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
366 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
367 include_latest=True)
367 include_latest=True)
368
368
369 elif hist_access_type == 'range':
369 elif hist_access_type == 'range':
370 session = parent['content']['session']
370 session = parent['content']['session']
371 start = parent['content']['start']
371 start = parent['content']['start']
372 stop = parent['content']['stop']
372 stop = parent['content']['stop']
373 hist = self.shell.history_manager.get_range(session, start, stop,
373 hist = self.shell.history_manager.get_range(session, start, stop,
374 raw=raw, output=output)
374 raw=raw, output=output)
375
375
376 elif hist_access_type == 'search':
376 elif hist_access_type == 'search':
377 pattern = parent['content']['pattern']
377 pattern = parent['content']['pattern']
378 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
378 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
379
379
380 else:
380 else:
381 hist = []
381 hist = []
382 content = {'history' : list(hist)}
382 content = {'history' : list(hist)}
383 content = json_clean(content)
383 content = json_clean(content)
384 msg = self.session.send(self.shell_socket, 'history_reply',
384 msg = self.session.send(self.shell_socket, 'history_reply',
385 content, parent, ident)
385 content, parent, ident)
386 self.log.debug(str(msg))
386 self.log.debug(str(msg))
387
387
388 def connect_request(self, ident, parent):
388 def connect_request(self, ident, parent):
389 if self._recorded_ports is not None:
389 if self._recorded_ports is not None:
390 content = self._recorded_ports.copy()
390 content = self._recorded_ports.copy()
391 else:
391 else:
392 content = {}
392 content = {}
393 msg = self.session.send(self.shell_socket, 'connect_reply',
393 msg = self.session.send(self.shell_socket, 'connect_reply',
394 content, parent, ident)
394 content, parent, ident)
395 self.log.debug(msg)
395 self.log.debug(msg)
396
396
397 def shutdown_request(self, ident, parent):
397 def shutdown_request(self, ident, parent):
398 self.shell.exit_now = True
398 self.shell.exit_now = True
399 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
399 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
400 sys.exit(0)
400 sys.exit(0)
401
401
402 #---------------------------------------------------------------------------
402 #---------------------------------------------------------------------------
403 # Protected interface
403 # Protected interface
404 #---------------------------------------------------------------------------
404 #---------------------------------------------------------------------------
405
405
406 def _abort_queue(self):
406 def _abort_queue(self):
407 while True:
407 while True:
408 try:
408 try:
409 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
409 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
410 except Exception:
410 except Exception:
411 self.log.warn("Invalid Message:", exc_info=True)
411 self.log.warn("Invalid Message:", exc_info=True)
412 continue
412 continue
413 if msg is None:
413 if msg is None:
414 break
414 break
415 else:
415 else:
416 assert ident is not None, \
416 assert ident is not None, \
417 "Unexpected missing message part."
417 "Unexpected missing message part."
418
418
419 self.log.debug("Aborting:\n"+str(Message(msg)))
419 self.log.debug("Aborting:\n"+str(Message(msg)))
420 msg_type = msg['header']['msg_type']
420 msg_type = msg['header']['msg_type']
421 reply_type = msg_type.split('_')[0] + '_reply'
421 reply_type = msg_type.split('_')[0] + '_reply'
422 reply_msg = self.session.send(self.shell_socket, reply_type,
422 reply_msg = self.session.send(self.shell_socket, reply_type,
423 {'status' : 'aborted'}, msg, ident=ident)
423 {'status' : 'aborted'}, msg, ident=ident)
424 self.log.debug(reply_msg)
424 self.log.debug(reply_msg)
425 # We need to wait a bit for requests to come in. This can probably
425 # We need to wait a bit for requests to come in. This can probably
426 # be set shorter for true asynchronous clients.
426 # be set shorter for true asynchronous clients.
427 time.sleep(0.1)
427 time.sleep(0.1)
428
428
429 def _no_raw_input(self):
429 def _no_raw_input(self):
430 """Raise StdinNotImplentedError if active frontend doesn't support stdin."""
430 """Raise StdinNotImplentedError if active frontend doesn't support stdin."""
431 raise StdinNotImplementedError("raw_input was called, but this frontend does not support stdin.")
431 raise StdinNotImplementedError("raw_input was called, but this frontend does not support stdin.")
432
432
433 def _raw_input(self, prompt, ident, parent):
433 def _raw_input(self, prompt, ident, parent):
434 # Flush output before making the request.
434 # Flush output before making the request.
435 sys.stderr.flush()
435 sys.stderr.flush()
436 sys.stdout.flush()
436 sys.stdout.flush()
437
437
438 # Send the input request.
438 # Send the input request.
439 content = json_clean(dict(prompt=prompt))
439 content = json_clean(dict(prompt=prompt))
440 msg = self.session.send(self.stdin_socket, u'input_request', content, parent, ident=ident)
440 msg = self.session.send(self.stdin_socket, u'input_request', content, parent, ident=ident)
441
441
442 # Await a response.
442 # Await a response.
443 while True:
443 while True:
444 try:
444 try:
445 ident, reply = self.session.recv(self.stdin_socket, 0)
445 ident, reply = self.session.recv(self.stdin_socket, 0)
446 except Exception:
446 except Exception:
447 self.log.warn("Invalid Message:", exc_info=True)
447 self.log.warn("Invalid Message:", exc_info=True)
448 else:
448 else:
449 break
449 break
450 try:
450 try:
451 value = reply['content']['value']
451 value = reply['content']['value']
452 except:
452 except:
453 self.log.error("Got bad raw_input reply: ")
453 self.log.error("Got bad raw_input reply: ")
454 self.log.error(str(Message(parent)))
454 self.log.error(str(Message(parent)))
455 value = ''
455 value = ''
456 return value
456 return value
457
457
458 def _complete(self, msg):
458 def _complete(self, msg):
459 c = msg['content']
459 c = msg['content']
460 try:
460 try:
461 cpos = int(c['cursor_pos'])
461 cpos = int(c['cursor_pos'])
462 except:
462 except:
463 # If we don't get something that we can convert to an integer, at
463 # If we don't get something that we can convert to an integer, at
464 # least attempt the completion guessing the cursor is at the end of
464 # least attempt the completion guessing the cursor is at the end of
465 # the text, if there's any, and otherwise of the line
465 # the text, if there's any, and otherwise of the line
466 cpos = len(c['text'])
466 cpos = len(c['text'])
467 if cpos==0:
467 if cpos==0:
468 cpos = len(c['line'])
468 cpos = len(c['line'])
469 return self.shell.complete(c['text'], c['line'], cpos)
469 return self.shell.complete(c['text'], c['line'], cpos)
470
470
471 def _object_info(self, context):
471 def _object_info(self, context):
472 symbol, leftover = self._symbol_from_context(context)
472 symbol, leftover = self._symbol_from_context(context)
473 if symbol is not None and not leftover:
473 if symbol is not None and not leftover:
474 doc = getattr(symbol, '__doc__', '')
474 doc = getattr(symbol, '__doc__', '')
475 else:
475 else:
476 doc = ''
476 doc = ''
477 object_info = dict(docstring = doc)
477 object_info = dict(docstring = doc)
478 return object_info
478 return object_info
479
479
480 def _symbol_from_context(self, context):
480 def _symbol_from_context(self, context):
481 if not context:
481 if not context:
482 return None, context
482 return None, context
483
483
484 base_symbol_string = context[0]
484 base_symbol_string = context[0]
485 symbol = self.shell.user_ns.get(base_symbol_string, None)
485 symbol = self.shell.user_ns.get(base_symbol_string, None)
486 if symbol is None:
486 if symbol is None:
487 symbol = __builtin__.__dict__.get(base_symbol_string, None)
487 symbol = __builtin__.__dict__.get(base_symbol_string, None)
488 if symbol is None:
488 if symbol is None:
489 return None, context
489 return None, context
490
490
491 context = context[1:]
491 context = context[1:]
492 for i, name in enumerate(context):
492 for i, name in enumerate(context):
493 new_symbol = getattr(symbol, name, None)
493 new_symbol = getattr(symbol, name, None)
494 if new_symbol is None:
494 if new_symbol is None:
495 return symbol, context[i:]
495 return symbol, context[i:]
496 else:
496 else:
497 symbol = new_symbol
497 symbol = new_symbol
498
498
499 return symbol, []
499 return symbol, []
500
500
501 def _at_shutdown(self):
501 def _at_shutdown(self):
502 """Actions taken at shutdown by the kernel, called by python's atexit.
502 """Actions taken at shutdown by the kernel, called by python's atexit.
503 """
503 """
504 # io.rprint("Kernel at_shutdown") # dbg
504 # io.rprint("Kernel at_shutdown") # dbg
505 if self._shutdown_message is not None:
505 if self._shutdown_message is not None:
506 self.session.send(self.shell_socket, self._shutdown_message)
506 self.session.send(self.shell_socket, self._shutdown_message)
507 self.session.send(self.iopub_socket, self._shutdown_message)
507 self.session.send(self.iopub_socket, self._shutdown_message)
508 self.log.debug(str(self._shutdown_message))
508 self.log.debug(str(self._shutdown_message))
509 # A very short sleep to give zmq time to flush its message buffers
509 # A very short sleep to give zmq time to flush its message buffers
510 # before Python truly shuts down.
510 # before Python truly shuts down.
511 time.sleep(0.01)
511 time.sleep(0.01)
512
512
513
513
514 #------------------------------------------------------------------------------
514 #------------------------------------------------------------------------------
515 # Eventloops for integrating the Kernel into different GUIs
515 # Eventloops for integrating the Kernel into different GUIs
516 #------------------------------------------------------------------------------
516 #------------------------------------------------------------------------------
517
517
518
518
519 def loop_qt4(kernel):
519 def loop_qt4(kernel):
520 """Start a kernel with PyQt4 event loop integration."""
520 """Start a kernel with PyQt4 event loop integration."""
521
521
522 from IPython.external.qt_for_kernel import QtCore
522 from IPython.external.qt_for_kernel import QtCore
523 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
523 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
524
524
525 kernel.app = get_app_qt4([" "])
525 kernel.app = get_app_qt4([" "])
526 kernel.app.setQuitOnLastWindowClosed(False)
526 kernel.app.setQuitOnLastWindowClosed(False)
527 kernel.timer = QtCore.QTimer()
527 kernel.timer = QtCore.QTimer()
528 kernel.timer.timeout.connect(kernel.do_one_iteration)
528 kernel.timer.timeout.connect(kernel.do_one_iteration)
529 # Units for the timer are in milliseconds
529 # Units for the timer are in milliseconds
530 kernel.timer.start(1000*kernel._poll_interval)
530 kernel.timer.start(1000*kernel._poll_interval)
531 start_event_loop_qt4(kernel.app)
531 start_event_loop_qt4(kernel.app)
532
532
533
533
534 def loop_wx(kernel):
534 def loop_wx(kernel):
535 """Start a kernel with wx event loop support."""
535 """Start a kernel with wx event loop support."""
536
536
537 import wx
537 import wx
538 from IPython.lib.guisupport import start_event_loop_wx
538 from IPython.lib.guisupport import start_event_loop_wx
539
539
540 doi = kernel.do_one_iteration
540 doi = kernel.do_one_iteration
541 # Wx uses milliseconds
541 # Wx uses milliseconds
542 poll_interval = int(1000*kernel._poll_interval)
542 poll_interval = int(1000*kernel._poll_interval)
543
543
544 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
544 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
545 # We make the Frame hidden when we create it in the main app below.
545 # We make the Frame hidden when we create it in the main app below.
546 class TimerFrame(wx.Frame):
546 class TimerFrame(wx.Frame):
547 def __init__(self, func):
547 def __init__(self, func):
548 wx.Frame.__init__(self, None, -1)
548 wx.Frame.__init__(self, None, -1)
549 self.timer = wx.Timer(self)
549 self.timer = wx.Timer(self)
550 # Units for the timer are in milliseconds
550 # Units for the timer are in milliseconds
551 self.timer.Start(poll_interval)
551 self.timer.Start(poll_interval)
552 self.Bind(wx.EVT_TIMER, self.on_timer)
552 self.Bind(wx.EVT_TIMER, self.on_timer)
553 self.func = func
553 self.func = func
554
554
555 def on_timer(self, event):
555 def on_timer(self, event):
556 self.func()
556 self.func()
557
557
558 # We need a custom wx.App to create our Frame subclass that has the
558 # We need a custom wx.App to create our Frame subclass that has the
559 # wx.Timer to drive the ZMQ event loop.
559 # wx.Timer to drive the ZMQ event loop.
560 class IPWxApp(wx.App):
560 class IPWxApp(wx.App):
561 def OnInit(self):
561 def OnInit(self):
562 self.frame = TimerFrame(doi)
562 self.frame = TimerFrame(doi)
563 self.frame.Show(False)
563 self.frame.Show(False)
564 return True
564 return True
565
565
566 # The redirect=False here makes sure that wx doesn't replace
566 # The redirect=False here makes sure that wx doesn't replace
567 # sys.stdout/stderr with its own classes.
567 # sys.stdout/stderr with its own classes.
568 kernel.app = IPWxApp(redirect=False)
568 kernel.app = IPWxApp(redirect=False)
569 start_event_loop_wx(kernel.app)
569 start_event_loop_wx(kernel.app)
570
570
571
571
572 def loop_tk(kernel):
572 def loop_tk(kernel):
573 """Start a kernel with the Tk event loop."""
573 """Start a kernel with the Tk event loop."""
574
574
575 import Tkinter
575 import Tkinter
576 doi = kernel.do_one_iteration
576 doi = kernel.do_one_iteration
577 # Tk uses milliseconds
577 # Tk uses milliseconds
578 poll_interval = int(1000*kernel._poll_interval)
578 poll_interval = int(1000*kernel._poll_interval)
579 # For Tkinter, we create a Tk object and call its withdraw method.
579 # For Tkinter, we create a Tk object and call its withdraw method.
580 class Timer(object):
580 class Timer(object):
581 def __init__(self, func):
581 def __init__(self, func):
582 self.app = Tkinter.Tk()
582 self.app = Tkinter.Tk()
583 self.app.withdraw()
583 self.app.withdraw()
584 self.func = func
584 self.func = func
585
585
586 def on_timer(self):
586 def on_timer(self):
587 self.func()
587 self.func()
588 self.app.after(poll_interval, self.on_timer)
588 self.app.after(poll_interval, self.on_timer)
589
589
590 def start(self):
590 def start(self):
591 self.on_timer() # Call it once to get things going.
591 self.on_timer() # Call it once to get things going.
592 self.app.mainloop()
592 self.app.mainloop()
593
593
594 kernel.timer = Timer(doi)
594 kernel.timer = Timer(doi)
595 kernel.timer.start()
595 kernel.timer.start()
596
596
597
597
598 def loop_gtk(kernel):
598 def loop_gtk(kernel):
599 """Start the kernel, coordinating with the GTK event loop"""
599 """Start the kernel, coordinating with the GTK event loop"""
600 from .gui.gtkembed import GTKEmbed
600 from .gui.gtkembed import GTKEmbed
601
601
602 gtk_kernel = GTKEmbed(kernel)
602 gtk_kernel = GTKEmbed(kernel)
603 gtk_kernel.start()
603 gtk_kernel.start()
604
604
605
605
606 def loop_cocoa(kernel):
606 def loop_cocoa(kernel):
607 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
607 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
608 via the matplotlib MacOSX backend.
608 via the matplotlib MacOSX backend.
609 """
609 """
610 import matplotlib
610 import matplotlib
611 if matplotlib.__version__ < '1.1.0':
611 if matplotlib.__version__ < '1.1.0':
612 kernel.log.warn(
612 kernel.log.warn(
613 "MacOSX backend in matplotlib %s doesn't have a Timer, "
613 "MacOSX backend in matplotlib %s doesn't have a Timer, "
614 "falling back on Tk for CFRunLoop integration. Note that "
614 "falling back on Tk for CFRunLoop integration. Note that "
615 "even this won't work if Tk is linked against X11 instead of "
615 "even this won't work if Tk is linked against X11 instead of "
616 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
616 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
617 "you must use matplotlib >= 1.1.0, or a native libtk."
617 "you must use matplotlib >= 1.1.0, or a native libtk."
618 )
618 )
619 return loop_tk(kernel)
619 return loop_tk(kernel)
620
620
621 from matplotlib.backends.backend_macosx import TimerMac, show
621 from matplotlib.backends.backend_macosx import TimerMac, show
622
622
623 # scale interval for sec->ms
623 # scale interval for sec->ms
624 poll_interval = int(1000*kernel._poll_interval)
624 poll_interval = int(1000*kernel._poll_interval)
625
625
626 real_excepthook = sys.excepthook
626 real_excepthook = sys.excepthook
627 def handle_int(etype, value, tb):
627 def handle_int(etype, value, tb):
628 """don't let KeyboardInterrupts look like crashes"""
628 """don't let KeyboardInterrupts look like crashes"""
629 if etype is KeyboardInterrupt:
629 if etype is KeyboardInterrupt:
630 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
630 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
631 else:
631 else:
632 real_excepthook(etype, value, tb)
632 real_excepthook(etype, value, tb)
633
633
634 # add doi() as a Timer to the CFRunLoop
634 # add doi() as a Timer to the CFRunLoop
635 def doi():
635 def doi():
636 # restore excepthook during IPython code
636 # restore excepthook during IPython code
637 sys.excepthook = real_excepthook
637 sys.excepthook = real_excepthook
638 kernel.do_one_iteration()
638 kernel.do_one_iteration()
639 # and back:
639 # and back:
640 sys.excepthook = handle_int
640 sys.excepthook = handle_int
641
641
642 t = TimerMac(poll_interval)
642 t = TimerMac(poll_interval)
643 t.add_callback(doi)
643 t.add_callback(doi)
644 t.start()
644 t.start()
645
645
646 # but still need a Poller for when there are no active windows,
646 # but still need a Poller for when there are no active windows,
647 # during which time mainloop() returns immediately
647 # during which time mainloop() returns immediately
648 poller = zmq.Poller()
648 poller = zmq.Poller()
649 poller.register(kernel.shell_socket, zmq.POLLIN)
649 poller.register(kernel.shell_socket, zmq.POLLIN)
650
650
651 while True:
651 while True:
652 try:
652 try:
653 # double nested try/except, to properly catch KeyboardInterrupt
653 # double nested try/except, to properly catch KeyboardInterrupt
654 # due to pyzmq Issue #130
654 # due to pyzmq Issue #130
655 try:
655 try:
656 # don't let interrupts during mainloop invoke crash_handler:
656 # don't let interrupts during mainloop invoke crash_handler:
657 sys.excepthook = handle_int
657 sys.excepthook = handle_int
658 show.mainloop()
658 show.mainloop()
659 sys.excepthook = real_excepthook
659 sys.excepthook = real_excepthook
660 # use poller if mainloop returned (no windows)
660 # use poller if mainloop returned (no windows)
661 # scale by extra factor of 10, since it's a real poll
661 # scale by extra factor of 10, since it's a real poll
662 poller.poll(10*poll_interval)
662 poller.poll(10*poll_interval)
663 kernel.do_one_iteration()
663 kernel.do_one_iteration()
664 except:
664 except:
665 raise
665 raise
666 except KeyboardInterrupt:
666 except KeyboardInterrupt:
667 # Ctrl-C shouldn't crash the kernel
667 # Ctrl-C shouldn't crash the kernel
668 io.raw_print("KeyboardInterrupt caught in kernel")
668 io.raw_print("KeyboardInterrupt caught in kernel")
669 finally:
669 finally:
670 # ensure excepthook is restored
670 # ensure excepthook is restored
671 sys.excepthook = real_excepthook
671 sys.excepthook = real_excepthook
672
672
673 # mapping of keys to loop functions
673 # mapping of keys to loop functions
674 loop_map = {
674 loop_map = {
675 'qt' : loop_qt4,
675 'qt' : loop_qt4,
676 'qt4': loop_qt4,
676 'qt4': loop_qt4,
677 'inline': None,
677 'inline': None,
678 'osx': loop_cocoa,
678 'osx': loop_cocoa,
679 'wx' : loop_wx,
679 'wx' : loop_wx,
680 'tk' : loop_tk,
680 'tk' : loop_tk,
681 'gtk': loop_gtk,
681 'gtk': loop_gtk,
682 }
682 }
683
683
684 def enable_gui(gui, kernel=None):
684 def enable_gui(gui, kernel=None):
685 """Enable integration with a give GUI"""
685 """Enable integration with a give GUI"""
686 if kernel is None:
686 if kernel is None:
687 kernel = IPKernelApp.instance().kernel
687 kernel = IPKernelApp.instance().kernel
688 if gui not in loop_map:
688 if gui not in loop_map:
689 raise ValueError("GUI %r not supported" % gui)
689 raise ValueError("GUI %r not supported" % gui)
690 loop = loop_map[gui]
690 loop = loop_map[gui]
691 if kernel.eventloop is not None and kernel.eventloop is not loop:
691 if kernel.eventloop is not None and kernel.eventloop is not loop:
692 raise RuntimeError("Cannot activate multiple GUI eventloops")
692 raise RuntimeError("Cannot activate multiple GUI eventloops")
693 kernel.eventloop = loop
693 kernel.eventloop = loop
694
694
695
695
696 #-----------------------------------------------------------------------------
696 #-----------------------------------------------------------------------------
697 # Aliases and Flags for the IPKernelApp
697 # Aliases and Flags for the IPKernelApp
698 #-----------------------------------------------------------------------------
698 #-----------------------------------------------------------------------------
699
699
700 flags = dict(kernel_flags)
700 flags = dict(kernel_flags)
701 flags.update(shell_flags)
701 flags.update(shell_flags)
702
702
703 addflag = lambda *args: flags.update(boolean_flag(*args))
703 addflag = lambda *args: flags.update(boolean_flag(*args))
704
704
705 flags['pylab'] = (
705 flags['pylab'] = (
706 {'IPKernelApp' : {'pylab' : 'auto'}},
706 {'IPKernelApp' : {'pylab' : 'auto'}},
707 """Pre-load matplotlib and numpy for interactive use with
707 """Pre-load matplotlib and numpy for interactive use with
708 the default matplotlib backend."""
708 the default matplotlib backend."""
709 )
709 )
710
710
711 aliases = dict(kernel_aliases)
711 aliases = dict(kernel_aliases)
712 aliases.update(shell_aliases)
712 aliases.update(shell_aliases)
713
713
714 # it's possible we don't want short aliases for *all* of these:
714 # it's possible we don't want short aliases for *all* of these:
715 aliases.update(dict(
715 aliases.update(dict(
716 pylab='IPKernelApp.pylab',
716 pylab='IPKernelApp.pylab',
717 ))
717 ))
718
718
719 #-----------------------------------------------------------------------------
719 #-----------------------------------------------------------------------------
720 # The IPKernelApp class
720 # The IPKernelApp class
721 #-----------------------------------------------------------------------------
721 #-----------------------------------------------------------------------------
722
722
723 class IPKernelApp(KernelApp, InteractiveShellApp):
723 class IPKernelApp(KernelApp, InteractiveShellApp):
724 name = 'ipkernel'
724 name = 'ipkernel'
725
725
726 aliases = Dict(aliases)
726 aliases = Dict(aliases)
727 flags = Dict(flags)
727 flags = Dict(flags)
728 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
728 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
729 # configurables
729 # configurables
730 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
730 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
731 config=True,
731 config=True,
732 help="""Pre-load matplotlib and numpy for interactive use,
732 help="""Pre-load matplotlib and numpy for interactive use,
733 selecting a particular matplotlib backend and loop integration.
733 selecting a particular matplotlib backend and loop integration.
734 """
734 """
735 )
735 )
736
736
737 @catch_config_error
737 @catch_config_error
738 def initialize(self, argv=None):
738 def initialize(self, argv=None):
739 super(IPKernelApp, self).initialize(argv)
739 super(IPKernelApp, self).initialize(argv)
740 self.init_shell()
740 self.init_shell()
741 self.init_extensions()
741 self.init_extensions()
742 self.init_code()
742 self.init_code()
743
743
744 def init_kernel(self):
744 def init_kernel(self):
745
745
746 kernel = Kernel(config=self.config, session=self.session,
746 kernel = Kernel(config=self.config, session=self.session,
747 shell_socket=self.shell_socket,
747 shell_socket=self.shell_socket,
748 iopub_socket=self.iopub_socket,
748 iopub_socket=self.iopub_socket,
749 stdin_socket=self.stdin_socket,
749 stdin_socket=self.stdin_socket,
750 log=self.log,
750 log=self.log,
751 profile_dir=self.profile_dir,
751 profile_dir=self.profile_dir,
752 )
752 )
753 self.kernel = kernel
753 self.kernel = kernel
754 kernel.record_ports(self.ports)
754 kernel.record_ports(self.ports)
755 shell = kernel.shell
755 shell = kernel.shell
756 if self.pylab:
756 if self.pylab:
757 try:
757 try:
758 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
758 gui, backend = pylabtools.find_gui_and_backend(self.pylab)
759 shell.enable_pylab(gui, import_all=self.pylab_import_all)
759 shell.enable_pylab(gui, import_all=self.pylab_import_all)
760 except Exception:
760 except Exception:
761 self.log.error("Pylab initialization failed", exc_info=True)
761 self.log.error("Pylab initialization failed", exc_info=True)
762 # print exception straight to stdout, because normally
762 # print exception straight to stdout, because normally
763 # _showtraceback associates the reply with an execution,
763 # _showtraceback associates the reply with an execution,
764 # which means frontends will never draw it, as this exception
764 # which means frontends will never draw it, as this exception
765 # is not associated with any execute request.
765 # is not associated with any execute request.
766
766
767 # replace pyerr-sending traceback with stdout
767 # replace pyerr-sending traceback with stdout
768 _showtraceback = shell._showtraceback
768 _showtraceback = shell._showtraceback
769 def print_tb(etype, evalue, stb):
769 def print_tb(etype, evalue, stb):
770 print ("Error initializing pylab, pylab mode will not be active", file=io.stderr)
770 print ("Error initializing pylab, pylab mode will not be active", file=io.stderr)
771 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
771 print (shell.InteractiveTB.stb2text(stb), file=io.stdout)
772 shell._showtraceback = print_tb
772 shell._showtraceback = print_tb
773
773
774 # send the traceback over stdout
774 # send the traceback over stdout
775 shell.showtraceback(tb_offset=0)
775 shell.showtraceback(tb_offset=0)
776
776
777 # restore proper _showtraceback method
777 # restore proper _showtraceback method
778 shell._showtraceback = _showtraceback
778 shell._showtraceback = _showtraceback
779
779
780
780
781 def init_shell(self):
781 def init_shell(self):
782 self.shell = self.kernel.shell
782 self.shell = self.kernel.shell
783 self.shell.configurables.append(self)
783
784
784
785
785 #-----------------------------------------------------------------------------
786 #-----------------------------------------------------------------------------
786 # Kernel main and launch functions
787 # Kernel main and launch functions
787 #-----------------------------------------------------------------------------
788 #-----------------------------------------------------------------------------
788
789
789 def launch_kernel(*args, **kwargs):
790 def launch_kernel(*args, **kwargs):
790 """Launches a localhost IPython kernel, binding to the specified ports.
791 """Launches a localhost IPython kernel, binding to the specified ports.
791
792
792 This function simply calls entry_point.base_launch_kernel with the right first
793 This function simply calls entry_point.base_launch_kernel with the right first
793 command to start an ipkernel. See base_launch_kernel for arguments.
794 command to start an ipkernel. See base_launch_kernel for arguments.
794
795
795 Returns
796 Returns
796 -------
797 -------
797 A tuple of form:
798 A tuple of form:
798 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
799 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
799 where kernel_process is a Popen object and the ports are integers.
800 where kernel_process is a Popen object and the ports are integers.
800 """
801 """
801 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
802 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
802 *args, **kwargs)
803 *args, **kwargs)
803
804
804
805
805 def main():
806 def main():
806 """Run an IPKernel as an application"""
807 """Run an IPKernel as an application"""
807 app = IPKernelApp.instance()
808 app = IPKernelApp.instance()
808 app.initialize()
809 app.initialize()
809 app.start()
810 app.start()
810
811
811
812
812 if __name__ == '__main__':
813 if __name__ == '__main__':
813 main()
814 main()
General Comments 0
You need to be logged in to leave comments. Login now