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