##// END OF EJS Templates
allow_none=False by default for Type and Instance
Sylvain Corlay -
Show More

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

@@ -1,622 +1,622 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """A base class for a configurable application."""
2 """A base class for a configurable application."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import json
9 import json
10 import logging
10 import logging
11 import os
11 import os
12 import re
12 import re
13 import sys
13 import sys
14 from copy import deepcopy
14 from copy import deepcopy
15 from collections import defaultdict
15 from collections import defaultdict
16
16
17 from decorator import decorator
17 from decorator import decorator
18
18
19 from IPython.config.configurable import SingletonConfigurable
19 from IPython.config.configurable import SingletonConfigurable
20 from IPython.config.loader import (
20 from IPython.config.loader import (
21 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
21 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
22 )
22 )
23
23
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import (
25 Unicode, List, Enum, Dict, Instance, TraitError
25 Unicode, List, Enum, Dict, Instance, TraitError
26 )
26 )
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils.text import indent, wrap_paragraphs, dedent
28 from IPython.utils.text import indent, wrap_paragraphs, dedent
29 from IPython.utils import py3compat
29 from IPython.utils import py3compat
30 from IPython.utils.py3compat import string_types, iteritems
30 from IPython.utils.py3compat import string_types, iteritems
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Descriptions for the various sections
33 # Descriptions for the various sections
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 # merge flags&aliases into options
36 # merge flags&aliases into options
37 option_description = """
37 option_description = """
38 Arguments that take values are actually convenience aliases to full
38 Arguments that take values are actually convenience aliases to full
39 Configurables, whose aliases are listed on the help line. For more information
39 Configurables, whose aliases are listed on the help line. For more information
40 on full configurables, see '--help-all'.
40 on full configurables, see '--help-all'.
41 """.strip() # trim newlines of front and back
41 """.strip() # trim newlines of front and back
42
42
43 keyvalue_description = """
43 keyvalue_description = """
44 Parameters are set from command-line arguments of the form:
44 Parameters are set from command-line arguments of the form:
45 `--Class.trait=value`.
45 `--Class.trait=value`.
46 This line is evaluated in Python, so simple expressions are allowed, e.g.::
46 This line is evaluated in Python, so simple expressions are allowed, e.g.::
47 `--C.a='range(3)'` For setting C.a=[0,1,2].
47 `--C.a='range(3)'` For setting C.a=[0,1,2].
48 """.strip() # trim newlines of front and back
48 """.strip() # trim newlines of front and back
49
49
50 # sys.argv can be missing, for example when python is embedded. See the docs
50 # sys.argv can be missing, for example when python is embedded. See the docs
51 # for details: http://docs.python.org/2/c-api/intro.html#embedding-python
51 # for details: http://docs.python.org/2/c-api/intro.html#embedding-python
52 if not hasattr(sys, "argv"):
52 if not hasattr(sys, "argv"):
53 sys.argv = [""]
53 sys.argv = [""]
54
54
55 subcommand_description = """
55 subcommand_description = """
56 Subcommands are launched as `{app} cmd [args]`. For information on using
56 Subcommands are launched as `{app} cmd [args]`. For information on using
57 subcommand 'cmd', do: `{app} cmd -h`.
57 subcommand 'cmd', do: `{app} cmd -h`.
58 """
58 """
59 # get running program name
59 # get running program name
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Application class
62 # Application class
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 @decorator
65 @decorator
66 def catch_config_error(method, app, *args, **kwargs):
66 def catch_config_error(method, app, *args, **kwargs):
67 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
67 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
68
68
69 On a TraitError (generally caused by bad config), this will print the trait's
69 On a TraitError (generally caused by bad config), this will print the trait's
70 message, and exit the app.
70 message, and exit the app.
71
71
72 For use on init methods, to prevent invoking excepthook on invalid input.
72 For use on init methods, to prevent invoking excepthook on invalid input.
73 """
73 """
74 try:
74 try:
75 return method(app, *args, **kwargs)
75 return method(app, *args, **kwargs)
76 except (TraitError, ArgumentError) as e:
76 except (TraitError, ArgumentError) as e:
77 app.print_help()
77 app.print_help()
78 app.log.fatal("Bad config encountered during initialization:")
78 app.log.fatal("Bad config encountered during initialization:")
79 app.log.fatal(str(e))
79 app.log.fatal(str(e))
80 app.log.debug("Config at the time: %s", app.config)
80 app.log.debug("Config at the time: %s", app.config)
81 app.exit(1)
81 app.exit(1)
82
82
83
83
84 class ApplicationError(Exception):
84 class ApplicationError(Exception):
85 pass
85 pass
86
86
87 class LevelFormatter(logging.Formatter):
87 class LevelFormatter(logging.Formatter):
88 """Formatter with additional `highlevel` record
88 """Formatter with additional `highlevel` record
89
89
90 This field is empty if log level is less than highlevel_limit,
90 This field is empty if log level is less than highlevel_limit,
91 otherwise it is formatted with self.highlevel_format.
91 otherwise it is formatted with self.highlevel_format.
92
92
93 Useful for adding 'WARNING' to warning messages,
93 Useful for adding 'WARNING' to warning messages,
94 without adding 'INFO' to info, etc.
94 without adding 'INFO' to info, etc.
95 """
95 """
96 highlevel_limit = logging.WARN
96 highlevel_limit = logging.WARN
97 highlevel_format = " %(levelname)s |"
97 highlevel_format = " %(levelname)s |"
98
98
99 def format(self, record):
99 def format(self, record):
100 if record.levelno >= self.highlevel_limit:
100 if record.levelno >= self.highlevel_limit:
101 record.highlevel = self.highlevel_format % record.__dict__
101 record.highlevel = self.highlevel_format % record.__dict__
102 else:
102 else:
103 record.highlevel = ""
103 record.highlevel = ""
104 return super(LevelFormatter, self).format(record)
104 return super(LevelFormatter, self).format(record)
105
105
106
106
107 class Application(SingletonConfigurable):
107 class Application(SingletonConfigurable):
108 """A singleton application with full configuration support."""
108 """A singleton application with full configuration support."""
109
109
110 # The name of the application, will usually match the name of the command
110 # The name of the application, will usually match the name of the command
111 # line application
111 # line application
112 name = Unicode(u'application')
112 name = Unicode(u'application')
113
113
114 # The description of the application that is printed at the beginning
114 # The description of the application that is printed at the beginning
115 # of the help.
115 # of the help.
116 description = Unicode(u'This is an application.')
116 description = Unicode(u'This is an application.')
117 # default section descriptions
117 # default section descriptions
118 option_description = Unicode(option_description)
118 option_description = Unicode(option_description)
119 keyvalue_description = Unicode(keyvalue_description)
119 keyvalue_description = Unicode(keyvalue_description)
120 subcommand_description = Unicode(subcommand_description)
120 subcommand_description = Unicode(subcommand_description)
121
121
122 python_config_loader_class = PyFileConfigLoader
122 python_config_loader_class = PyFileConfigLoader
123 json_config_loader_class = JSONFileConfigLoader
123 json_config_loader_class = JSONFileConfigLoader
124
124
125 # The usage and example string that goes at the end of the help string.
125 # The usage and example string that goes at the end of the help string.
126 examples = Unicode()
126 examples = Unicode()
127
127
128 # A sequence of Configurable subclasses whose config=True attributes will
128 # A sequence of Configurable subclasses whose config=True attributes will
129 # be exposed at the command line.
129 # be exposed at the command line.
130 classes = []
130 classes = []
131 @property
131 @property
132 def _help_classes(self):
132 def _help_classes(self):
133 """Define `App.help_classes` if CLI classes should differ from config file classes"""
133 """Define `App.help_classes` if CLI classes should differ from config file classes"""
134 return getattr(self, 'help_classes', self.classes)
134 return getattr(self, 'help_classes', self.classes)
135
135
136 @property
136 @property
137 def _config_classes(self):
137 def _config_classes(self):
138 """Define `App.config_classes` if config file classes should differ from CLI classes."""
138 """Define `App.config_classes` if config file classes should differ from CLI classes."""
139 return getattr(self, 'config_classes', self.classes)
139 return getattr(self, 'config_classes', self.classes)
140
140
141 # The version string of this application.
141 # The version string of this application.
142 version = Unicode(u'0.0')
142 version = Unicode(u'0.0')
143
143
144 # the argv used to initialize the application
144 # the argv used to initialize the application
145 argv = List()
145 argv = List()
146
146
147 # The log level for the application
147 # The log level for the application
148 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
148 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
149 default_value=logging.WARN,
149 default_value=logging.WARN,
150 config=True,
150 config=True,
151 help="Set the log level by value or name.")
151 help="Set the log level by value or name.")
152 def _log_level_changed(self, name, old, new):
152 def _log_level_changed(self, name, old, new):
153 """Adjust the log level when log_level is set."""
153 """Adjust the log level when log_level is set."""
154 if isinstance(new, string_types):
154 if isinstance(new, string_types):
155 new = getattr(logging, new)
155 new = getattr(logging, new)
156 self.log_level = new
156 self.log_level = new
157 self.log.setLevel(new)
157 self.log.setLevel(new)
158
158
159 _log_formatter_cls = LevelFormatter
159 _log_formatter_cls = LevelFormatter
160
160
161 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
161 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
162 help="The date format used by logging formatters for %(asctime)s"
162 help="The date format used by logging formatters for %(asctime)s"
163 )
163 )
164 def _log_datefmt_changed(self, name, old, new):
164 def _log_datefmt_changed(self, name, old, new):
165 self._log_format_changed('log_format', self.log_format, self.log_format)
165 self._log_format_changed('log_format', self.log_format, self.log_format)
166
166
167 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
167 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
168 help="The Logging format template",
168 help="The Logging format template",
169 )
169 )
170 def _log_format_changed(self, name, old, new):
170 def _log_format_changed(self, name, old, new):
171 """Change the log formatter when log_format is set."""
171 """Change the log formatter when log_format is set."""
172 _log_handler = self.log.handlers[0]
172 _log_handler = self.log.handlers[0]
173 _log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt)
173 _log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt)
174 _log_handler.setFormatter(_log_formatter)
174 _log_handler.setFormatter(_log_formatter)
175
175
176
176
177 log = Instance(logging.Logger)
177 log = Instance(logging.Logger, allow_none=True)
178 def _log_default(self):
178 def _log_default(self):
179 """Start logging for this application.
179 """Start logging for this application.
180
180
181 The default is to log to stderr using a StreamHandler, if no default
181 The default is to log to stderr using a StreamHandler, if no default
182 handler already exists. The log level starts at logging.WARN, but this
182 handler already exists. The log level starts at logging.WARN, but this
183 can be adjusted by setting the ``log_level`` attribute.
183 can be adjusted by setting the ``log_level`` attribute.
184 """
184 """
185 log = logging.getLogger(self.__class__.__name__)
185 log = logging.getLogger(self.__class__.__name__)
186 log.setLevel(self.log_level)
186 log.setLevel(self.log_level)
187 log.propagate = False
187 log.propagate = False
188 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
188 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
189 while _log:
189 while _log:
190 if _log.handlers:
190 if _log.handlers:
191 return log
191 return log
192 if not _log.propagate:
192 if not _log.propagate:
193 break
193 break
194 else:
194 else:
195 _log = _log.parent
195 _log = _log.parent
196 if sys.executable.endswith('pythonw.exe'):
196 if sys.executable.endswith('pythonw.exe'):
197 # this should really go to a file, but file-logging is only
197 # this should really go to a file, but file-logging is only
198 # hooked up in parallel applications
198 # hooked up in parallel applications
199 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
199 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
200 else:
200 else:
201 _log_handler = logging.StreamHandler()
201 _log_handler = logging.StreamHandler()
202 _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
202 _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
203 _log_handler.setFormatter(_log_formatter)
203 _log_handler.setFormatter(_log_formatter)
204 log.addHandler(_log_handler)
204 log.addHandler(_log_handler)
205 return log
205 return log
206
206
207 # the alias map for configurables
207 # the alias map for configurables
208 aliases = Dict({'log-level' : 'Application.log_level'})
208 aliases = Dict({'log-level' : 'Application.log_level'})
209
209
210 # flags for loading Configurables or store_const style flags
210 # flags for loading Configurables or store_const style flags
211 # flags are loaded from this dict by '--key' flags
211 # flags are loaded from this dict by '--key' flags
212 # this must be a dict of two-tuples, the first element being the Config/dict
212 # this must be a dict of two-tuples, the first element being the Config/dict
213 # and the second being the help string for the flag
213 # and the second being the help string for the flag
214 flags = Dict()
214 flags = Dict()
215 def _flags_changed(self, name, old, new):
215 def _flags_changed(self, name, old, new):
216 """ensure flags dict is valid"""
216 """ensure flags dict is valid"""
217 for key,value in iteritems(new):
217 for key,value in iteritems(new):
218 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
218 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
219 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
219 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
220 assert isinstance(value[1], string_types), "Bad flag: %r:%s"%(key,value)
220 assert isinstance(value[1], string_types), "Bad flag: %r:%s"%(key,value)
221
221
222
222
223 # subcommands for launching other applications
223 # subcommands for launching other applications
224 # if this is not empty, this will be a parent Application
224 # if this is not empty, this will be a parent Application
225 # this must be a dict of two-tuples,
225 # this must be a dict of two-tuples,
226 # the first element being the application class/import string
226 # the first element being the application class/import string
227 # and the second being the help string for the subcommand
227 # and the second being the help string for the subcommand
228 subcommands = Dict()
228 subcommands = Dict()
229 # parse_command_line will initialize a subapp, if requested
229 # parse_command_line will initialize a subapp, if requested
230 subapp = Instance('IPython.config.application.Application', allow_none=True)
230 subapp = Instance('IPython.config.application.Application', allow_none=True)
231
231
232 # extra command-line arguments that don't set config values
232 # extra command-line arguments that don't set config values
233 extra_args = List(Unicode)
233 extra_args = List(Unicode)
234
234
235
235
236 def __init__(self, **kwargs):
236 def __init__(self, **kwargs):
237 SingletonConfigurable.__init__(self, **kwargs)
237 SingletonConfigurable.__init__(self, **kwargs)
238 # Ensure my class is in self.classes, so my attributes appear in command line
238 # Ensure my class is in self.classes, so my attributes appear in command line
239 # options and config files.
239 # options and config files.
240 if self.__class__ not in self.classes:
240 if self.__class__ not in self.classes:
241 self.classes.insert(0, self.__class__)
241 self.classes.insert(0, self.__class__)
242
242
243 def _config_changed(self, name, old, new):
243 def _config_changed(self, name, old, new):
244 SingletonConfigurable._config_changed(self, name, old, new)
244 SingletonConfigurable._config_changed(self, name, old, new)
245 self.log.debug('Config changed:')
245 self.log.debug('Config changed:')
246 self.log.debug(repr(new))
246 self.log.debug(repr(new))
247
247
248 @catch_config_error
248 @catch_config_error
249 def initialize(self, argv=None):
249 def initialize(self, argv=None):
250 """Do the basic steps to configure me.
250 """Do the basic steps to configure me.
251
251
252 Override in subclasses.
252 Override in subclasses.
253 """
253 """
254 self.parse_command_line(argv)
254 self.parse_command_line(argv)
255
255
256
256
257 def start(self):
257 def start(self):
258 """Start the app mainloop.
258 """Start the app mainloop.
259
259
260 Override in subclasses.
260 Override in subclasses.
261 """
261 """
262 if self.subapp is not None:
262 if self.subapp is not None:
263 return self.subapp.start()
263 return self.subapp.start()
264
264
265 def print_alias_help(self):
265 def print_alias_help(self):
266 """Print the alias part of the help."""
266 """Print the alias part of the help."""
267 if not self.aliases:
267 if not self.aliases:
268 return
268 return
269
269
270 lines = []
270 lines = []
271 classdict = {}
271 classdict = {}
272 for cls in self._help_classes:
272 for cls in self._help_classes:
273 # include all parents (up to, but excluding Configurable) in available names
273 # include all parents (up to, but excluding Configurable) in available names
274 for c in cls.mro()[:-3]:
274 for c in cls.mro()[:-3]:
275 classdict[c.__name__] = c
275 classdict[c.__name__] = c
276
276
277 for alias, longname in iteritems(self.aliases):
277 for alias, longname in iteritems(self.aliases):
278 classname, traitname = longname.split('.',1)
278 classname, traitname = longname.split('.',1)
279 cls = classdict[classname]
279 cls = classdict[classname]
280
280
281 trait = cls.class_traits(config=True)[traitname]
281 trait = cls.class_traits(config=True)[traitname]
282 help = cls.class_get_trait_help(trait).splitlines()
282 help = cls.class_get_trait_help(trait).splitlines()
283 # reformat first line
283 # reformat first line
284 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
284 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
285 if len(alias) == 1:
285 if len(alias) == 1:
286 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
286 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
287 lines.extend(help)
287 lines.extend(help)
288 # lines.append('')
288 # lines.append('')
289 print(os.linesep.join(lines))
289 print(os.linesep.join(lines))
290
290
291 def print_flag_help(self):
291 def print_flag_help(self):
292 """Print the flag part of the help."""
292 """Print the flag part of the help."""
293 if not self.flags:
293 if not self.flags:
294 return
294 return
295
295
296 lines = []
296 lines = []
297 for m, (cfg,help) in iteritems(self.flags):
297 for m, (cfg,help) in iteritems(self.flags):
298 prefix = '--' if len(m) > 1 else '-'
298 prefix = '--' if len(m) > 1 else '-'
299 lines.append(prefix+m)
299 lines.append(prefix+m)
300 lines.append(indent(dedent(help.strip())))
300 lines.append(indent(dedent(help.strip())))
301 # lines.append('')
301 # lines.append('')
302 print(os.linesep.join(lines))
302 print(os.linesep.join(lines))
303
303
304 def print_options(self):
304 def print_options(self):
305 if not self.flags and not self.aliases:
305 if not self.flags and not self.aliases:
306 return
306 return
307 lines = ['Options']
307 lines = ['Options']
308 lines.append('-'*len(lines[0]))
308 lines.append('-'*len(lines[0]))
309 lines.append('')
309 lines.append('')
310 for p in wrap_paragraphs(self.option_description):
310 for p in wrap_paragraphs(self.option_description):
311 lines.append(p)
311 lines.append(p)
312 lines.append('')
312 lines.append('')
313 print(os.linesep.join(lines))
313 print(os.linesep.join(lines))
314 self.print_flag_help()
314 self.print_flag_help()
315 self.print_alias_help()
315 self.print_alias_help()
316 print()
316 print()
317
317
318 def print_subcommands(self):
318 def print_subcommands(self):
319 """Print the subcommand part of the help."""
319 """Print the subcommand part of the help."""
320 if not self.subcommands:
320 if not self.subcommands:
321 return
321 return
322
322
323 lines = ["Subcommands"]
323 lines = ["Subcommands"]
324 lines.append('-'*len(lines[0]))
324 lines.append('-'*len(lines[0]))
325 lines.append('')
325 lines.append('')
326 for p in wrap_paragraphs(self.subcommand_description.format(
326 for p in wrap_paragraphs(self.subcommand_description.format(
327 app=self.name)):
327 app=self.name)):
328 lines.append(p)
328 lines.append(p)
329 lines.append('')
329 lines.append('')
330 for subc, (cls, help) in iteritems(self.subcommands):
330 for subc, (cls, help) in iteritems(self.subcommands):
331 lines.append(subc)
331 lines.append(subc)
332 if help:
332 if help:
333 lines.append(indent(dedent(help.strip())))
333 lines.append(indent(dedent(help.strip())))
334 lines.append('')
334 lines.append('')
335 print(os.linesep.join(lines))
335 print(os.linesep.join(lines))
336
336
337 def print_help(self, classes=False):
337 def print_help(self, classes=False):
338 """Print the help for each Configurable class in self.classes.
338 """Print the help for each Configurable class in self.classes.
339
339
340 If classes=False (the default), only flags and aliases are printed.
340 If classes=False (the default), only flags and aliases are printed.
341 """
341 """
342 self.print_description()
342 self.print_description()
343 self.print_subcommands()
343 self.print_subcommands()
344 self.print_options()
344 self.print_options()
345
345
346 if classes:
346 if classes:
347 help_classes = self._help_classes
347 help_classes = self._help_classes
348 if help_classes:
348 if help_classes:
349 print("Class parameters")
349 print("Class parameters")
350 print("----------------")
350 print("----------------")
351 print()
351 print()
352 for p in wrap_paragraphs(self.keyvalue_description):
352 for p in wrap_paragraphs(self.keyvalue_description):
353 print(p)
353 print(p)
354 print()
354 print()
355
355
356 for cls in help_classes:
356 for cls in help_classes:
357 cls.class_print_help()
357 cls.class_print_help()
358 print()
358 print()
359 else:
359 else:
360 print("To see all available configurables, use `--help-all`")
360 print("To see all available configurables, use `--help-all`")
361 print()
361 print()
362
362
363 self.print_examples()
363 self.print_examples()
364
364
365
365
366 def print_description(self):
366 def print_description(self):
367 """Print the application description."""
367 """Print the application description."""
368 for p in wrap_paragraphs(self.description):
368 for p in wrap_paragraphs(self.description):
369 print(p)
369 print(p)
370 print()
370 print()
371
371
372 def print_examples(self):
372 def print_examples(self):
373 """Print usage and examples.
373 """Print usage and examples.
374
374
375 This usage string goes at the end of the command line help string
375 This usage string goes at the end of the command line help string
376 and should contain examples of the application's usage.
376 and should contain examples of the application's usage.
377 """
377 """
378 if self.examples:
378 if self.examples:
379 print("Examples")
379 print("Examples")
380 print("--------")
380 print("--------")
381 print()
381 print()
382 print(indent(dedent(self.examples.strip())))
382 print(indent(dedent(self.examples.strip())))
383 print()
383 print()
384
384
385 def print_version(self):
385 def print_version(self):
386 """Print the version string."""
386 """Print the version string."""
387 print(self.version)
387 print(self.version)
388
388
389 def update_config(self, config):
389 def update_config(self, config):
390 """Fire the traits events when the config is updated."""
390 """Fire the traits events when the config is updated."""
391 # Save a copy of the current config.
391 # Save a copy of the current config.
392 newconfig = deepcopy(self.config)
392 newconfig = deepcopy(self.config)
393 # Merge the new config into the current one.
393 # Merge the new config into the current one.
394 newconfig.merge(config)
394 newconfig.merge(config)
395 # Save the combined config as self.config, which triggers the traits
395 # Save the combined config as self.config, which triggers the traits
396 # events.
396 # events.
397 self.config = newconfig
397 self.config = newconfig
398
398
399 @catch_config_error
399 @catch_config_error
400 def initialize_subcommand(self, subc, argv=None):
400 def initialize_subcommand(self, subc, argv=None):
401 """Initialize a subcommand with argv."""
401 """Initialize a subcommand with argv."""
402 subapp,help = self.subcommands.get(subc)
402 subapp,help = self.subcommands.get(subc)
403
403
404 if isinstance(subapp, string_types):
404 if isinstance(subapp, string_types):
405 subapp = import_item(subapp)
405 subapp = import_item(subapp)
406
406
407 # clear existing instances
407 # clear existing instances
408 self.__class__.clear_instance()
408 self.__class__.clear_instance()
409 # instantiate
409 # instantiate
410 self.subapp = subapp.instance(config=self.config)
410 self.subapp = subapp.instance(config=self.config)
411 # and initialize subapp
411 # and initialize subapp
412 self.subapp.initialize(argv)
412 self.subapp.initialize(argv)
413
413
414 def flatten_flags(self):
414 def flatten_flags(self):
415 """flatten flags and aliases, so cl-args override as expected.
415 """flatten flags and aliases, so cl-args override as expected.
416
416
417 This prevents issues such as an alias pointing to InteractiveShell,
417 This prevents issues such as an alias pointing to InteractiveShell,
418 but a config file setting the same trait in TerminalInteraciveShell
418 but a config file setting the same trait in TerminalInteraciveShell
419 getting inappropriate priority over the command-line arg.
419 getting inappropriate priority over the command-line arg.
420
420
421 Only aliases with exactly one descendent in the class list
421 Only aliases with exactly one descendent in the class list
422 will be promoted.
422 will be promoted.
423
423
424 """
424 """
425 # build a tree of classes in our list that inherit from a particular
425 # build a tree of classes in our list that inherit from a particular
426 # it will be a dict by parent classname of classes in our list
426 # it will be a dict by parent classname of classes in our list
427 # that are descendents
427 # that are descendents
428 mro_tree = defaultdict(list)
428 mro_tree = defaultdict(list)
429 for cls in self._help_classes:
429 for cls in self._help_classes:
430 clsname = cls.__name__
430 clsname = cls.__name__
431 for parent in cls.mro()[1:-3]:
431 for parent in cls.mro()[1:-3]:
432 # exclude cls itself and Configurable,HasTraits,object
432 # exclude cls itself and Configurable,HasTraits,object
433 mro_tree[parent.__name__].append(clsname)
433 mro_tree[parent.__name__].append(clsname)
434 # flatten aliases, which have the form:
434 # flatten aliases, which have the form:
435 # { 'alias' : 'Class.trait' }
435 # { 'alias' : 'Class.trait' }
436 aliases = {}
436 aliases = {}
437 for alias, cls_trait in iteritems(self.aliases):
437 for alias, cls_trait in iteritems(self.aliases):
438 cls,trait = cls_trait.split('.',1)
438 cls,trait = cls_trait.split('.',1)
439 children = mro_tree[cls]
439 children = mro_tree[cls]
440 if len(children) == 1:
440 if len(children) == 1:
441 # exactly one descendent, promote alias
441 # exactly one descendent, promote alias
442 cls = children[0]
442 cls = children[0]
443 aliases[alias] = '.'.join([cls,trait])
443 aliases[alias] = '.'.join([cls,trait])
444
444
445 # flatten flags, which are of the form:
445 # flatten flags, which are of the form:
446 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
446 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
447 flags = {}
447 flags = {}
448 for key, (flagdict, help) in iteritems(self.flags):
448 for key, (flagdict, help) in iteritems(self.flags):
449 newflag = {}
449 newflag = {}
450 for cls, subdict in iteritems(flagdict):
450 for cls, subdict in iteritems(flagdict):
451 children = mro_tree[cls]
451 children = mro_tree[cls]
452 # exactly one descendent, promote flag section
452 # exactly one descendent, promote flag section
453 if len(children) == 1:
453 if len(children) == 1:
454 cls = children[0]
454 cls = children[0]
455 newflag[cls] = subdict
455 newflag[cls] = subdict
456 flags[key] = (newflag, help)
456 flags[key] = (newflag, help)
457 return flags, aliases
457 return flags, aliases
458
458
459 @catch_config_error
459 @catch_config_error
460 def parse_command_line(self, argv=None):
460 def parse_command_line(self, argv=None):
461 """Parse the command line arguments."""
461 """Parse the command line arguments."""
462 argv = sys.argv[1:] if argv is None else argv
462 argv = sys.argv[1:] if argv is None else argv
463 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
463 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
464
464
465 if argv and argv[0] == 'help':
465 if argv and argv[0] == 'help':
466 # turn `ipython help notebook` into `ipython notebook -h`
466 # turn `ipython help notebook` into `ipython notebook -h`
467 argv = argv[1:] + ['-h']
467 argv = argv[1:] + ['-h']
468
468
469 if self.subcommands and len(argv) > 0:
469 if self.subcommands and len(argv) > 0:
470 # we have subcommands, and one may have been specified
470 # we have subcommands, and one may have been specified
471 subc, subargv = argv[0], argv[1:]
471 subc, subargv = argv[0], argv[1:]
472 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
472 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
473 # it's a subcommand, and *not* a flag or class parameter
473 # it's a subcommand, and *not* a flag or class parameter
474 return self.initialize_subcommand(subc, subargv)
474 return self.initialize_subcommand(subc, subargv)
475
475
476 # Arguments after a '--' argument are for the script IPython may be
476 # Arguments after a '--' argument are for the script IPython may be
477 # about to run, not IPython iteslf. For arguments parsed here (help and
477 # about to run, not IPython iteslf. For arguments parsed here (help and
478 # version), we want to only search the arguments up to the first
478 # version), we want to only search the arguments up to the first
479 # occurrence of '--', which we're calling interpreted_argv.
479 # occurrence of '--', which we're calling interpreted_argv.
480 try:
480 try:
481 interpreted_argv = argv[:argv.index('--')]
481 interpreted_argv = argv[:argv.index('--')]
482 except ValueError:
482 except ValueError:
483 interpreted_argv = argv
483 interpreted_argv = argv
484
484
485 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
485 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
486 self.print_help('--help-all' in interpreted_argv)
486 self.print_help('--help-all' in interpreted_argv)
487 self.exit(0)
487 self.exit(0)
488
488
489 if '--version' in interpreted_argv or '-V' in interpreted_argv:
489 if '--version' in interpreted_argv or '-V' in interpreted_argv:
490 self.print_version()
490 self.print_version()
491 self.exit(0)
491 self.exit(0)
492
492
493 # flatten flags&aliases, so cl-args get appropriate priority:
493 # flatten flags&aliases, so cl-args get appropriate priority:
494 flags,aliases = self.flatten_flags()
494 flags,aliases = self.flatten_flags()
495 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
495 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
496 flags=flags, log=self.log)
496 flags=flags, log=self.log)
497 config = loader.load_config()
497 config = loader.load_config()
498 self.update_config(config)
498 self.update_config(config)
499 # store unparsed args in extra_args
499 # store unparsed args in extra_args
500 self.extra_args = loader.extra_args
500 self.extra_args = loader.extra_args
501
501
502 @classmethod
502 @classmethod
503 def _load_config_files(cls, basefilename, path=None, log=None):
503 def _load_config_files(cls, basefilename, path=None, log=None):
504 """Load config files (py,json) by filename and path.
504 """Load config files (py,json) by filename and path.
505
505
506 yield each config object in turn.
506 yield each config object in turn.
507 """
507 """
508
508
509 if not isinstance(path, list):
509 if not isinstance(path, list):
510 path = [path]
510 path = [path]
511 for path in path[::-1]:
511 for path in path[::-1]:
512 # path list is in descending priority order, so load files backwards:
512 # path list is in descending priority order, so load files backwards:
513 pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
513 pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
514 jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
514 jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
515 config = None
515 config = None
516 for loader in [pyloader, jsonloader]:
516 for loader in [pyloader, jsonloader]:
517 try:
517 try:
518 config = loader.load_config()
518 config = loader.load_config()
519 except ConfigFileNotFound:
519 except ConfigFileNotFound:
520 pass
520 pass
521 except Exception:
521 except Exception:
522 # try to get the full filename, but it will be empty in the
522 # try to get the full filename, but it will be empty in the
523 # unlikely event that the error raised before filefind finished
523 # unlikely event that the error raised before filefind finished
524 filename = loader.full_filename or basefilename
524 filename = loader.full_filename or basefilename
525 # problem while running the file
525 # problem while running the file
526 if log:
526 if log:
527 log.error("Exception while loading config file %s",
527 log.error("Exception while loading config file %s",
528 filename, exc_info=True)
528 filename, exc_info=True)
529 else:
529 else:
530 if log:
530 if log:
531 log.debug("Loaded config file: %s", loader.full_filename)
531 log.debug("Loaded config file: %s", loader.full_filename)
532 if config:
532 if config:
533 yield config
533 yield config
534
534
535 raise StopIteration
535 raise StopIteration
536
536
537
537
538 @catch_config_error
538 @catch_config_error
539 def load_config_file(self, filename, path=None):
539 def load_config_file(self, filename, path=None):
540 """Load config files by filename and path."""
540 """Load config files by filename and path."""
541 filename, ext = os.path.splitext(filename)
541 filename, ext = os.path.splitext(filename)
542 loaded = []
542 loaded = []
543 for config in self._load_config_files(filename, path=path, log=self.log):
543 for config in self._load_config_files(filename, path=path, log=self.log):
544 loaded.append(config)
544 loaded.append(config)
545 self.update_config(config)
545 self.update_config(config)
546 if len(loaded) > 1:
546 if len(loaded) > 1:
547 collisions = loaded[0].collisions(loaded[1])
547 collisions = loaded[0].collisions(loaded[1])
548 if collisions:
548 if collisions:
549 self.log.warn("Collisions detected in {0}.py and {0}.json config files."
549 self.log.warn("Collisions detected in {0}.py and {0}.json config files."
550 " {0}.json has higher priority: {1}".format(
550 " {0}.json has higher priority: {1}".format(
551 filename, json.dumps(collisions, indent=2),
551 filename, json.dumps(collisions, indent=2),
552 ))
552 ))
553
553
554
554
555 def generate_config_file(self):
555 def generate_config_file(self):
556 """generate default config file from Configurables"""
556 """generate default config file from Configurables"""
557 lines = ["# Configuration file for %s." % self.name]
557 lines = ["# Configuration file for %s." % self.name]
558 lines.append('')
558 lines.append('')
559 for cls in self._config_classes:
559 for cls in self._config_classes:
560 lines.append(cls.class_config_section())
560 lines.append(cls.class_config_section())
561 return '\n'.join(lines)
561 return '\n'.join(lines)
562
562
563 def exit(self, exit_status=0):
563 def exit(self, exit_status=0):
564 self.log.debug("Exiting application: %s" % self.name)
564 self.log.debug("Exiting application: %s" % self.name)
565 sys.exit(exit_status)
565 sys.exit(exit_status)
566
566
567 @classmethod
567 @classmethod
568 def launch_instance(cls, argv=None, **kwargs):
568 def launch_instance(cls, argv=None, **kwargs):
569 """Launch a global instance of this Application
569 """Launch a global instance of this Application
570
570
571 If a global instance already exists, this reinitializes and starts it
571 If a global instance already exists, this reinitializes and starts it
572 """
572 """
573 app = cls.instance(**kwargs)
573 app = cls.instance(**kwargs)
574 app.initialize(argv)
574 app.initialize(argv)
575 app.start()
575 app.start()
576
576
577 #-----------------------------------------------------------------------------
577 #-----------------------------------------------------------------------------
578 # utility functions, for convenience
578 # utility functions, for convenience
579 #-----------------------------------------------------------------------------
579 #-----------------------------------------------------------------------------
580
580
581 def boolean_flag(name, configurable, set_help='', unset_help=''):
581 def boolean_flag(name, configurable, set_help='', unset_help=''):
582 """Helper for building basic --trait, --no-trait flags.
582 """Helper for building basic --trait, --no-trait flags.
583
583
584 Parameters
584 Parameters
585 ----------
585 ----------
586
586
587 name : str
587 name : str
588 The name of the flag.
588 The name of the flag.
589 configurable : str
589 configurable : str
590 The 'Class.trait' string of the trait to be set/unset with the flag
590 The 'Class.trait' string of the trait to be set/unset with the flag
591 set_help : unicode
591 set_help : unicode
592 help string for --name flag
592 help string for --name flag
593 unset_help : unicode
593 unset_help : unicode
594 help string for --no-name flag
594 help string for --no-name flag
595
595
596 Returns
596 Returns
597 -------
597 -------
598
598
599 cfg : dict
599 cfg : dict
600 A dict with two keys: 'name', and 'no-name', for setting and unsetting
600 A dict with two keys: 'name', and 'no-name', for setting and unsetting
601 the trait, respectively.
601 the trait, respectively.
602 """
602 """
603 # default helpstrings
603 # default helpstrings
604 set_help = set_help or "set %s=True"%configurable
604 set_help = set_help or "set %s=True"%configurable
605 unset_help = unset_help or "set %s=False"%configurable
605 unset_help = unset_help or "set %s=False"%configurable
606
606
607 cls,trait = configurable.split('.')
607 cls,trait = configurable.split('.')
608
608
609 setter = {cls : {trait : True}}
609 setter = {cls : {trait : True}}
610 unsetter = {cls : {trait : False}}
610 unsetter = {cls : {trait : False}}
611 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
611 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
612
612
613
613
614 def get_config():
614 def get_config():
615 """Get the config object for the global Application instance, if there is one
615 """Get the config object for the global Application instance, if there is one
616
616
617 otherwise return an empty config object
617 otherwise return an empty config object
618 """
618 """
619 if Application.initialized():
619 if Application.initialized():
620 return Application.instance().config
620 return Application.instance().config
621 else:
621 else:
622 return Config()
622 return Config()
@@ -1,380 +1,380 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """A base class for objects that are configurable."""
2 """A base class for objects that are configurable."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import logging
9 import logging
10 from copy import deepcopy
10 from copy import deepcopy
11
11
12 from .loader import Config, LazyConfigValue
12 from .loader import Config, LazyConfigValue
13 from IPython.utils.traitlets import HasTraits, Instance
13 from IPython.utils.traitlets import HasTraits, Instance
14 from IPython.utils.text import indent, wrap_paragraphs
14 from IPython.utils.text import indent, wrap_paragraphs
15 from IPython.utils.py3compat import iteritems
15 from IPython.utils.py3compat import iteritems
16
16
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Helper classes for Configurables
19 # Helper classes for Configurables
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22
22
23 class ConfigurableError(Exception):
23 class ConfigurableError(Exception):
24 pass
24 pass
25
25
26
26
27 class MultipleInstanceError(ConfigurableError):
27 class MultipleInstanceError(ConfigurableError):
28 pass
28 pass
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Configurable implementation
31 # Configurable implementation
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 class Configurable(HasTraits):
34 class Configurable(HasTraits):
35
35
36 config = Instance(Config, (), {})
36 config = Instance(Config, (), {})
37 parent = Instance('IPython.config.configurable.Configurable')
37 parent = Instance('IPython.config.configurable.Configurable', allow_none=True)
38
38
39 def __init__(self, **kwargs):
39 def __init__(self, **kwargs):
40 """Create a configurable given a config config.
40 """Create a configurable given a config config.
41
41
42 Parameters
42 Parameters
43 ----------
43 ----------
44 config : Config
44 config : Config
45 If this is empty, default values are used. If config is a
45 If this is empty, default values are used. If config is a
46 :class:`Config` instance, it will be used to configure the
46 :class:`Config` instance, it will be used to configure the
47 instance.
47 instance.
48 parent : Configurable instance, optional
48 parent : Configurable instance, optional
49 The parent Configurable instance of this object.
49 The parent Configurable instance of this object.
50
50
51 Notes
51 Notes
52 -----
52 -----
53 Subclasses of Configurable must call the :meth:`__init__` method of
53 Subclasses of Configurable must call the :meth:`__init__` method of
54 :class:`Configurable` *before* doing anything else and using
54 :class:`Configurable` *before* doing anything else and using
55 :func:`super`::
55 :func:`super`::
56
56
57 class MyConfigurable(Configurable):
57 class MyConfigurable(Configurable):
58 def __init__(self, config=None):
58 def __init__(self, config=None):
59 super(MyConfigurable, self).__init__(config=config)
59 super(MyConfigurable, self).__init__(config=config)
60 # Then any other code you need to finish initialization.
60 # Then any other code you need to finish initialization.
61
61
62 This ensures that instances will be configured properly.
62 This ensures that instances will be configured properly.
63 """
63 """
64 parent = kwargs.pop('parent', None)
64 parent = kwargs.pop('parent', None)
65 if parent is not None:
65 if parent is not None:
66 # config is implied from parent
66 # config is implied from parent
67 if kwargs.get('config', None) is None:
67 if kwargs.get('config', None) is None:
68 kwargs['config'] = parent.config
68 kwargs['config'] = parent.config
69 self.parent = parent
69 self.parent = parent
70
70
71 config = kwargs.pop('config', None)
71 config = kwargs.pop('config', None)
72
72
73 # load kwarg traits, other than config
73 # load kwarg traits, other than config
74 super(Configurable, self).__init__(**kwargs)
74 super(Configurable, self).__init__(**kwargs)
75
75
76 # load config
76 # load config
77 if config is not None:
77 if config is not None:
78 # We used to deepcopy, but for now we are trying to just save
78 # We used to deepcopy, but for now we are trying to just save
79 # by reference. This *could* have side effects as all components
79 # by reference. This *could* have side effects as all components
80 # will share config. In fact, I did find such a side effect in
80 # will share config. In fact, I did find such a side effect in
81 # _config_changed below. If a config attribute value was a mutable type
81 # _config_changed below. If a config attribute value was a mutable type
82 # all instances of a component were getting the same copy, effectively
82 # all instances of a component were getting the same copy, effectively
83 # making that a class attribute.
83 # making that a class attribute.
84 # self.config = deepcopy(config)
84 # self.config = deepcopy(config)
85 self.config = config
85 self.config = config
86 else:
86 else:
87 # allow _config_default to return something
87 # allow _config_default to return something
88 self._load_config(self.config)
88 self._load_config(self.config)
89
89
90 # Ensure explicit kwargs are applied after loading config.
90 # Ensure explicit kwargs are applied after loading config.
91 # This is usually redundant, but ensures config doesn't override
91 # This is usually redundant, but ensures config doesn't override
92 # explicitly assigned values.
92 # explicitly assigned values.
93 for key, value in kwargs.items():
93 for key, value in kwargs.items():
94 setattr(self, key, value)
94 setattr(self, key, value)
95
95
96 #-------------------------------------------------------------------------
96 #-------------------------------------------------------------------------
97 # Static trait notifiations
97 # Static trait notifiations
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99
99
100 @classmethod
100 @classmethod
101 def section_names(cls):
101 def section_names(cls):
102 """return section names as a list"""
102 """return section names as a list"""
103 return [c.__name__ for c in reversed(cls.__mro__) if
103 return [c.__name__ for c in reversed(cls.__mro__) if
104 issubclass(c, Configurable) and issubclass(cls, c)
104 issubclass(c, Configurable) and issubclass(cls, c)
105 ]
105 ]
106
106
107 def _find_my_config(self, cfg):
107 def _find_my_config(self, cfg):
108 """extract my config from a global Config object
108 """extract my config from a global Config object
109
109
110 will construct a Config object of only the config values that apply to me
110 will construct a Config object of only the config values that apply to me
111 based on my mro(), as well as those of my parent(s) if they exist.
111 based on my mro(), as well as those of my parent(s) if they exist.
112
112
113 If I am Bar and my parent is Foo, and their parent is Tim,
113 If I am Bar and my parent is Foo, and their parent is Tim,
114 this will return merge following config sections, in this order::
114 this will return merge following config sections, in this order::
115
115
116 [Bar, Foo.bar, Tim.Foo.Bar]
116 [Bar, Foo.bar, Tim.Foo.Bar]
117
117
118 With the last item being the highest priority.
118 With the last item being the highest priority.
119 """
119 """
120 cfgs = [cfg]
120 cfgs = [cfg]
121 if self.parent:
121 if self.parent:
122 cfgs.append(self.parent._find_my_config(cfg))
122 cfgs.append(self.parent._find_my_config(cfg))
123 my_config = Config()
123 my_config = Config()
124 for c in cfgs:
124 for c in cfgs:
125 for sname in self.section_names():
125 for sname in self.section_names():
126 # Don't do a blind getattr as that would cause the config to
126 # Don't do a blind getattr as that would cause the config to
127 # dynamically create the section with name Class.__name__.
127 # dynamically create the section with name Class.__name__.
128 if c._has_section(sname):
128 if c._has_section(sname):
129 my_config.merge(c[sname])
129 my_config.merge(c[sname])
130 return my_config
130 return my_config
131
131
132 def _load_config(self, cfg, section_names=None, traits=None):
132 def _load_config(self, cfg, section_names=None, traits=None):
133 """load traits from a Config object"""
133 """load traits from a Config object"""
134
134
135 if traits is None:
135 if traits is None:
136 traits = self.traits(config=True)
136 traits = self.traits(config=True)
137 if section_names is None:
137 if section_names is None:
138 section_names = self.section_names()
138 section_names = self.section_names()
139
139
140 my_config = self._find_my_config(cfg)
140 my_config = self._find_my_config(cfg)
141
141
142 # hold trait notifications until after all config has been loaded
142 # hold trait notifications until after all config has been loaded
143 with self.hold_trait_notifications():
143 with self.hold_trait_notifications():
144 for name, config_value in iteritems(my_config):
144 for name, config_value in iteritems(my_config):
145 if name in traits:
145 if name in traits:
146 if isinstance(config_value, LazyConfigValue):
146 if isinstance(config_value, LazyConfigValue):
147 # ConfigValue is a wrapper for using append / update on containers
147 # ConfigValue is a wrapper for using append / update on containers
148 # without having to copy the initial value
148 # without having to copy the initial value
149 initial = getattr(self, name)
149 initial = getattr(self, name)
150 config_value = config_value.get_value(initial)
150 config_value = config_value.get_value(initial)
151 # We have to do a deepcopy here if we don't deepcopy the entire
151 # We have to do a deepcopy here if we don't deepcopy the entire
152 # config object. If we don't, a mutable config_value will be
152 # config object. If we don't, a mutable config_value will be
153 # shared by all instances, effectively making it a class attribute.
153 # shared by all instances, effectively making it a class attribute.
154 setattr(self, name, deepcopy(config_value))
154 setattr(self, name, deepcopy(config_value))
155
155
156 def _config_changed(self, name, old, new):
156 def _config_changed(self, name, old, new):
157 """Update all the class traits having ``config=True`` as metadata.
157 """Update all the class traits having ``config=True`` as metadata.
158
158
159 For any class trait with a ``config`` metadata attribute that is
159 For any class trait with a ``config`` metadata attribute that is
160 ``True``, we update the trait with the value of the corresponding
160 ``True``, we update the trait with the value of the corresponding
161 config entry.
161 config entry.
162 """
162 """
163 # Get all traits with a config metadata entry that is True
163 # Get all traits with a config metadata entry that is True
164 traits = self.traits(config=True)
164 traits = self.traits(config=True)
165
165
166 # We auto-load config section for this class as well as any parent
166 # We auto-load config section for this class as well as any parent
167 # classes that are Configurable subclasses. This starts with Configurable
167 # classes that are Configurable subclasses. This starts with Configurable
168 # and works down the mro loading the config for each section.
168 # and works down the mro loading the config for each section.
169 section_names = self.section_names()
169 section_names = self.section_names()
170 self._load_config(new, traits=traits, section_names=section_names)
170 self._load_config(new, traits=traits, section_names=section_names)
171
171
172 def update_config(self, config):
172 def update_config(self, config):
173 """Fire the traits events when the config is updated."""
173 """Fire the traits events when the config is updated."""
174 # Save a copy of the current config.
174 # Save a copy of the current config.
175 newconfig = deepcopy(self.config)
175 newconfig = deepcopy(self.config)
176 # Merge the new config into the current one.
176 # Merge the new config into the current one.
177 newconfig.merge(config)
177 newconfig.merge(config)
178 # Save the combined config as self.config, which triggers the traits
178 # Save the combined config as self.config, which triggers the traits
179 # events.
179 # events.
180 self.config = newconfig
180 self.config = newconfig
181
181
182 @classmethod
182 @classmethod
183 def class_get_help(cls, inst=None):
183 def class_get_help(cls, inst=None):
184 """Get the help string for this class in ReST format.
184 """Get the help string for this class in ReST format.
185
185
186 If `inst` is given, it's current trait values will be used in place of
186 If `inst` is given, it's current trait values will be used in place of
187 class defaults.
187 class defaults.
188 """
188 """
189 assert inst is None or isinstance(inst, cls)
189 assert inst is None or isinstance(inst, cls)
190 final_help = []
190 final_help = []
191 final_help.append(u'%s options' % cls.__name__)
191 final_help.append(u'%s options' % cls.__name__)
192 final_help.append(len(final_help[0])*u'-')
192 final_help.append(len(final_help[0])*u'-')
193 for k, v in sorted(cls.class_traits(config=True).items()):
193 for k, v in sorted(cls.class_traits(config=True).items()):
194 help = cls.class_get_trait_help(v, inst)
194 help = cls.class_get_trait_help(v, inst)
195 final_help.append(help)
195 final_help.append(help)
196 return '\n'.join(final_help)
196 return '\n'.join(final_help)
197
197
198 @classmethod
198 @classmethod
199 def class_get_trait_help(cls, trait, inst=None):
199 def class_get_trait_help(cls, trait, inst=None):
200 """Get the help string for a single trait.
200 """Get the help string for a single trait.
201
201
202 If `inst` is given, it's current trait values will be used in place of
202 If `inst` is given, it's current trait values will be used in place of
203 the class default.
203 the class default.
204 """
204 """
205 assert inst is None or isinstance(inst, cls)
205 assert inst is None or isinstance(inst, cls)
206 lines = []
206 lines = []
207 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
207 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
208 lines.append(header)
208 lines.append(header)
209 if inst is not None:
209 if inst is not None:
210 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
210 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
211 else:
211 else:
212 try:
212 try:
213 dvr = repr(trait.get_default_value())
213 dvr = repr(trait.get_default_value())
214 except Exception:
214 except Exception:
215 dvr = None # ignore defaults we can't construct
215 dvr = None # ignore defaults we can't construct
216 if dvr is not None:
216 if dvr is not None:
217 if len(dvr) > 64:
217 if len(dvr) > 64:
218 dvr = dvr[:61]+'...'
218 dvr = dvr[:61]+'...'
219 lines.append(indent('Default: %s' % dvr, 4))
219 lines.append(indent('Default: %s' % dvr, 4))
220 if 'Enum' in trait.__class__.__name__:
220 if 'Enum' in trait.__class__.__name__:
221 # include Enum choices
221 # include Enum choices
222 lines.append(indent('Choices: %r' % (trait.values,)))
222 lines.append(indent('Choices: %r' % (trait.values,)))
223
223
224 help = trait.get_metadata('help')
224 help = trait.get_metadata('help')
225 if help is not None:
225 if help is not None:
226 help = '\n'.join(wrap_paragraphs(help, 76))
226 help = '\n'.join(wrap_paragraphs(help, 76))
227 lines.append(indent(help, 4))
227 lines.append(indent(help, 4))
228 return '\n'.join(lines)
228 return '\n'.join(lines)
229
229
230 @classmethod
230 @classmethod
231 def class_print_help(cls, inst=None):
231 def class_print_help(cls, inst=None):
232 """Get the help string for a single trait and print it."""
232 """Get the help string for a single trait and print it."""
233 print(cls.class_get_help(inst))
233 print(cls.class_get_help(inst))
234
234
235 @classmethod
235 @classmethod
236 def class_config_section(cls):
236 def class_config_section(cls):
237 """Get the config class config section"""
237 """Get the config class config section"""
238 def c(s):
238 def c(s):
239 """return a commented, wrapped block."""
239 """return a commented, wrapped block."""
240 s = '\n\n'.join(wrap_paragraphs(s, 78))
240 s = '\n\n'.join(wrap_paragraphs(s, 78))
241
241
242 return '# ' + s.replace('\n', '\n# ')
242 return '# ' + s.replace('\n', '\n# ')
243
243
244 # section header
244 # section header
245 breaker = '#' + '-'*78
245 breaker = '#' + '-'*78
246 s = "# %s configuration" % cls.__name__
246 s = "# %s configuration" % cls.__name__
247 lines = [breaker, s, breaker, '']
247 lines = [breaker, s, breaker, '']
248 # get the description trait
248 # get the description trait
249 desc = cls.class_traits().get('description')
249 desc = cls.class_traits().get('description')
250 if desc:
250 if desc:
251 desc = desc.default_value
251 desc = desc.default_value
252 else:
252 else:
253 # no description trait, use __doc__
253 # no description trait, use __doc__
254 desc = getattr(cls, '__doc__', '')
254 desc = getattr(cls, '__doc__', '')
255 if desc:
255 if desc:
256 lines.append(c(desc))
256 lines.append(c(desc))
257 lines.append('')
257 lines.append('')
258
258
259 parents = []
259 parents = []
260 for parent in cls.mro():
260 for parent in cls.mro():
261 # only include parents that are not base classes
261 # only include parents that are not base classes
262 # and are not the class itself
262 # and are not the class itself
263 # and have some configurable traits to inherit
263 # and have some configurable traits to inherit
264 if parent is not cls and issubclass(parent, Configurable) and \
264 if parent is not cls and issubclass(parent, Configurable) and \
265 parent.class_traits(config=True):
265 parent.class_traits(config=True):
266 parents.append(parent)
266 parents.append(parent)
267
267
268 if parents:
268 if parents:
269 pstr = ', '.join([ p.__name__ for p in parents ])
269 pstr = ', '.join([ p.__name__ for p in parents ])
270 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
270 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
271 lines.append('')
271 lines.append('')
272
272
273 for name, trait in iteritems(cls.class_traits(config=True)):
273 for name, trait in iteritems(cls.class_traits(config=True)):
274 help = trait.get_metadata('help') or ''
274 help = trait.get_metadata('help') or ''
275 lines.append(c(help))
275 lines.append(c(help))
276 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
276 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
277 lines.append('')
277 lines.append('')
278 return '\n'.join(lines)
278 return '\n'.join(lines)
279
279
280
280
281
281
282 class SingletonConfigurable(Configurable):
282 class SingletonConfigurable(Configurable):
283 """A configurable that only allows one instance.
283 """A configurable that only allows one instance.
284
284
285 This class is for classes that should only have one instance of itself
285 This class is for classes that should only have one instance of itself
286 or *any* subclass. To create and retrieve such a class use the
286 or *any* subclass. To create and retrieve such a class use the
287 :meth:`SingletonConfigurable.instance` method.
287 :meth:`SingletonConfigurable.instance` method.
288 """
288 """
289
289
290 _instance = None
290 _instance = None
291
291
292 @classmethod
292 @classmethod
293 def _walk_mro(cls):
293 def _walk_mro(cls):
294 """Walk the cls.mro() for parent classes that are also singletons
294 """Walk the cls.mro() for parent classes that are also singletons
295
295
296 For use in instance()
296 For use in instance()
297 """
297 """
298
298
299 for subclass in cls.mro():
299 for subclass in cls.mro():
300 if issubclass(cls, subclass) and \
300 if issubclass(cls, subclass) and \
301 issubclass(subclass, SingletonConfigurable) and \
301 issubclass(subclass, SingletonConfigurable) and \
302 subclass != SingletonConfigurable:
302 subclass != SingletonConfigurable:
303 yield subclass
303 yield subclass
304
304
305 @classmethod
305 @classmethod
306 def clear_instance(cls):
306 def clear_instance(cls):
307 """unset _instance for this class and singleton parents.
307 """unset _instance for this class and singleton parents.
308 """
308 """
309 if not cls.initialized():
309 if not cls.initialized():
310 return
310 return
311 for subclass in cls._walk_mro():
311 for subclass in cls._walk_mro():
312 if isinstance(subclass._instance, cls):
312 if isinstance(subclass._instance, cls):
313 # only clear instances that are instances
313 # only clear instances that are instances
314 # of the calling class
314 # of the calling class
315 subclass._instance = None
315 subclass._instance = None
316
316
317 @classmethod
317 @classmethod
318 def instance(cls, *args, **kwargs):
318 def instance(cls, *args, **kwargs):
319 """Returns a global instance of this class.
319 """Returns a global instance of this class.
320
320
321 This method create a new instance if none have previously been created
321 This method create a new instance if none have previously been created
322 and returns a previously created instance is one already exists.
322 and returns a previously created instance is one already exists.
323
323
324 The arguments and keyword arguments passed to this method are passed
324 The arguments and keyword arguments passed to this method are passed
325 on to the :meth:`__init__` method of the class upon instantiation.
325 on to the :meth:`__init__` method of the class upon instantiation.
326
326
327 Examples
327 Examples
328 --------
328 --------
329
329
330 Create a singleton class using instance, and retrieve it::
330 Create a singleton class using instance, and retrieve it::
331
331
332 >>> from IPython.config.configurable import SingletonConfigurable
332 >>> from IPython.config.configurable import SingletonConfigurable
333 >>> class Foo(SingletonConfigurable): pass
333 >>> class Foo(SingletonConfigurable): pass
334 >>> foo = Foo.instance()
334 >>> foo = Foo.instance()
335 >>> foo == Foo.instance()
335 >>> foo == Foo.instance()
336 True
336 True
337
337
338 Create a subclass that is retrived using the base class instance::
338 Create a subclass that is retrived using the base class instance::
339
339
340 >>> class Bar(SingletonConfigurable): pass
340 >>> class Bar(SingletonConfigurable): pass
341 >>> class Bam(Bar): pass
341 >>> class Bam(Bar): pass
342 >>> bam = Bam.instance()
342 >>> bam = Bam.instance()
343 >>> bam == Bar.instance()
343 >>> bam == Bar.instance()
344 True
344 True
345 """
345 """
346 # Create and save the instance
346 # Create and save the instance
347 if cls._instance is None:
347 if cls._instance is None:
348 inst = cls(*args, **kwargs)
348 inst = cls(*args, **kwargs)
349 # Now make sure that the instance will also be returned by
349 # Now make sure that the instance will also be returned by
350 # parent classes' _instance attribute.
350 # parent classes' _instance attribute.
351 for subclass in cls._walk_mro():
351 for subclass in cls._walk_mro():
352 subclass._instance = inst
352 subclass._instance = inst
353
353
354 if isinstance(cls._instance, cls):
354 if isinstance(cls._instance, cls):
355 return cls._instance
355 return cls._instance
356 else:
356 else:
357 raise MultipleInstanceError(
357 raise MultipleInstanceError(
358 'Multiple incompatible subclass instances of '
358 'Multiple incompatible subclass instances of '
359 '%s are being created.' % cls.__name__
359 '%s are being created.' % cls.__name__
360 )
360 )
361
361
362 @classmethod
362 @classmethod
363 def initialized(cls):
363 def initialized(cls):
364 """Has an instance been created?"""
364 """Has an instance been created?"""
365 return hasattr(cls, "_instance") and cls._instance is not None
365 return hasattr(cls, "_instance") and cls._instance is not None
366
366
367
367
368 class LoggingConfigurable(Configurable):
368 class LoggingConfigurable(Configurable):
369 """A parent class for Configurables that log.
369 """A parent class for Configurables that log.
370
370
371 Subclasses have a log trait, and the default behavior
371 Subclasses have a log trait, and the default behavior
372 is to get the logger from the currently running Application.
372 is to get the logger from the currently running Application.
373 """
373 """
374
374
375 log = Instance('logging.Logger')
375 log = Instance('logging.Logger', allow_none=True)
376 def _log_default(self):
376 def _log_default(self):
377 from IPython.utils import log
377 from IPython.utils import log
378 return log.get_logger()
378 return log.get_logger()
379
379
380
380
@@ -1,256 +1,256 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 System command aliases.
3 System command aliases.
4
4
5 Authors:
5 Authors:
6
6
7 * Fernando Perez
7 * Fernando Perez
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License.
14 # Distributed under the terms of the BSD License.
15 #
15 #
16 # The full license is in the file COPYING.txt, distributed with this software.
16 # The full license is in the file COPYING.txt, distributed with this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26
26
27 from IPython.config.configurable import Configurable
27 from IPython.config.configurable import Configurable
28 from IPython.core.error import UsageError
28 from IPython.core.error import UsageError
29
29
30 from IPython.utils.py3compat import string_types
30 from IPython.utils.py3compat import string_types
31 from IPython.utils.traitlets import List, Instance
31 from IPython.utils.traitlets import List, Instance
32 from IPython.utils.warn import error
32 from IPython.utils.warn import error
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Utilities
35 # Utilities
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 # This is used as the pattern for calls to split_user_input.
38 # This is used as the pattern for calls to split_user_input.
39 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
39 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
40
40
41 def default_aliases():
41 def default_aliases():
42 """Return list of shell aliases to auto-define.
42 """Return list of shell aliases to auto-define.
43 """
43 """
44 # Note: the aliases defined here should be safe to use on a kernel
44 # Note: the aliases defined here should be safe to use on a kernel
45 # regardless of what frontend it is attached to. Frontends that use a
45 # regardless of what frontend it is attached to. Frontends that use a
46 # kernel in-process can define additional aliases that will only work in
46 # kernel in-process can define additional aliases that will only work in
47 # their case. For example, things like 'less' or 'clear' that manipulate
47 # their case. For example, things like 'less' or 'clear' that manipulate
48 # the terminal should NOT be declared here, as they will only work if the
48 # the terminal should NOT be declared here, as they will only work if the
49 # kernel is running inside a true terminal, and not over the network.
49 # kernel is running inside a true terminal, and not over the network.
50
50
51 if os.name == 'posix':
51 if os.name == 'posix':
52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
54 ('cat', 'cat'),
54 ('cat', 'cat'),
55 ]
55 ]
56 # Useful set of ls aliases. The GNU and BSD options are a little
56 # Useful set of ls aliases. The GNU and BSD options are a little
57 # different, so we make aliases that provide as similar as possible
57 # different, so we make aliases that provide as similar as possible
58 # behavior in ipython, by passing the right flags for each platform
58 # behavior in ipython, by passing the right flags for each platform
59 if sys.platform.startswith('linux'):
59 if sys.platform.startswith('linux'):
60 ls_aliases = [('ls', 'ls -F --color'),
60 ls_aliases = [('ls', 'ls -F --color'),
61 # long ls
61 # long ls
62 ('ll', 'ls -F -o --color'),
62 ('ll', 'ls -F -o --color'),
63 # ls normal files only
63 # ls normal files only
64 ('lf', 'ls -F -o --color %l | grep ^-'),
64 ('lf', 'ls -F -o --color %l | grep ^-'),
65 # ls symbolic links
65 # ls symbolic links
66 ('lk', 'ls -F -o --color %l | grep ^l'),
66 ('lk', 'ls -F -o --color %l | grep ^l'),
67 # directories or links to directories,
67 # directories or links to directories,
68 ('ldir', 'ls -F -o --color %l | grep /$'),
68 ('ldir', 'ls -F -o --color %l | grep /$'),
69 # things which are executable
69 # things which are executable
70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
71 ]
71 ]
72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
74 # the -G switch and lack the ability to use colorized output.
74 # the -G switch and lack the ability to use colorized output.
75 ls_aliases = [('ls', 'ls -F'),
75 ls_aliases = [('ls', 'ls -F'),
76 # long ls
76 # long ls
77 ('ll', 'ls -F -l'),
77 ('ll', 'ls -F -l'),
78 # ls normal files only
78 # ls normal files only
79 ('lf', 'ls -F -l %l | grep ^-'),
79 ('lf', 'ls -F -l %l | grep ^-'),
80 # ls symbolic links
80 # ls symbolic links
81 ('lk', 'ls -F -l %l | grep ^l'),
81 ('lk', 'ls -F -l %l | grep ^l'),
82 # directories or links to directories,
82 # directories or links to directories,
83 ('ldir', 'ls -F -l %l | grep /$'),
83 ('ldir', 'ls -F -l %l | grep /$'),
84 # things which are executable
84 # things which are executable
85 ('lx', 'ls -F -l %l | grep ^-..x'),
85 ('lx', 'ls -F -l %l | grep ^-..x'),
86 ]
86 ]
87 else:
87 else:
88 # BSD, OSX, etc.
88 # BSD, OSX, etc.
89 ls_aliases = [('ls', 'ls -F -G'),
89 ls_aliases = [('ls', 'ls -F -G'),
90 # long ls
90 # long ls
91 ('ll', 'ls -F -l -G'),
91 ('ll', 'ls -F -l -G'),
92 # ls normal files only
92 # ls normal files only
93 ('lf', 'ls -F -l -G %l | grep ^-'),
93 ('lf', 'ls -F -l -G %l | grep ^-'),
94 # ls symbolic links
94 # ls symbolic links
95 ('lk', 'ls -F -l -G %l | grep ^l'),
95 ('lk', 'ls -F -l -G %l | grep ^l'),
96 # directories or links to directories,
96 # directories or links to directories,
97 ('ldir', 'ls -F -G -l %l | grep /$'),
97 ('ldir', 'ls -F -G -l %l | grep /$'),
98 # things which are executable
98 # things which are executable
99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
100 ]
100 ]
101 default_aliases = default_aliases + ls_aliases
101 default_aliases = default_aliases + ls_aliases
102 elif os.name in ['nt', 'dos']:
102 elif os.name in ['nt', 'dos']:
103 default_aliases = [('ls', 'dir /on'),
103 default_aliases = [('ls', 'dir /on'),
104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
107 ]
107 ]
108 else:
108 else:
109 default_aliases = []
109 default_aliases = []
110
110
111 return default_aliases
111 return default_aliases
112
112
113
113
114 class AliasError(Exception):
114 class AliasError(Exception):
115 pass
115 pass
116
116
117
117
118 class InvalidAliasError(AliasError):
118 class InvalidAliasError(AliasError):
119 pass
119 pass
120
120
121 class Alias(object):
121 class Alias(object):
122 """Callable object storing the details of one alias.
122 """Callable object storing the details of one alias.
123
123
124 Instances are registered as magic functions to allow use of aliases.
124 Instances are registered as magic functions to allow use of aliases.
125 """
125 """
126
126
127 # Prepare blacklist
127 # Prepare blacklist
128 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
128 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
129
129
130 def __init__(self, shell, name, cmd):
130 def __init__(self, shell, name, cmd):
131 self.shell = shell
131 self.shell = shell
132 self.name = name
132 self.name = name
133 self.cmd = cmd
133 self.cmd = cmd
134 self.nargs = self.validate()
134 self.nargs = self.validate()
135
135
136 def validate(self):
136 def validate(self):
137 """Validate the alias, and return the number of arguments."""
137 """Validate the alias, and return the number of arguments."""
138 if self.name in self.blacklist:
138 if self.name in self.blacklist:
139 raise InvalidAliasError("The name %s can't be aliased "
139 raise InvalidAliasError("The name %s can't be aliased "
140 "because it is a keyword or builtin." % self.name)
140 "because it is a keyword or builtin." % self.name)
141 try:
141 try:
142 caller = self.shell.magics_manager.magics['line'][self.name]
142 caller = self.shell.magics_manager.magics['line'][self.name]
143 except KeyError:
143 except KeyError:
144 pass
144 pass
145 else:
145 else:
146 if not isinstance(caller, Alias):
146 if not isinstance(caller, Alias):
147 raise InvalidAliasError("The name %s can't be aliased "
147 raise InvalidAliasError("The name %s can't be aliased "
148 "because it is another magic command." % self.name)
148 "because it is another magic command." % self.name)
149
149
150 if not (isinstance(self.cmd, string_types)):
150 if not (isinstance(self.cmd, string_types)):
151 raise InvalidAliasError("An alias command must be a string, "
151 raise InvalidAliasError("An alias command must be a string, "
152 "got: %r" % self.cmd)
152 "got: %r" % self.cmd)
153
153
154 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
154 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
155
155
156 if (nargs > 0) and (self.cmd.find('%l') >= 0):
156 if (nargs > 0) and (self.cmd.find('%l') >= 0):
157 raise InvalidAliasError('The %s and %l specifiers are mutually '
157 raise InvalidAliasError('The %s and %l specifiers are mutually '
158 'exclusive in alias definitions.')
158 'exclusive in alias definitions.')
159
159
160 return nargs
160 return nargs
161
161
162 def __repr__(self):
162 def __repr__(self):
163 return "<alias {} for {!r}>".format(self.name, self.cmd)
163 return "<alias {} for {!r}>".format(self.name, self.cmd)
164
164
165 def __call__(self, rest=''):
165 def __call__(self, rest=''):
166 cmd = self.cmd
166 cmd = self.cmd
167 nargs = self.nargs
167 nargs = self.nargs
168 # Expand the %l special to be the user's input line
168 # Expand the %l special to be the user's input line
169 if cmd.find('%l') >= 0:
169 if cmd.find('%l') >= 0:
170 cmd = cmd.replace('%l', rest)
170 cmd = cmd.replace('%l', rest)
171 rest = ''
171 rest = ''
172
172
173 if nargs==0:
173 if nargs==0:
174 if cmd.find('%%s') >= 1:
174 if cmd.find('%%s') >= 1:
175 cmd = cmd.replace('%%s', '%s')
175 cmd = cmd.replace('%%s', '%s')
176 # Simple, argument-less aliases
176 # Simple, argument-less aliases
177 cmd = '%s %s' % (cmd, rest)
177 cmd = '%s %s' % (cmd, rest)
178 else:
178 else:
179 # Handle aliases with positional arguments
179 # Handle aliases with positional arguments
180 args = rest.split(None, nargs)
180 args = rest.split(None, nargs)
181 if len(args) < nargs:
181 if len(args) < nargs:
182 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
182 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
183 (self.name, nargs, len(args)))
183 (self.name, nargs, len(args)))
184 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
184 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
185
185
186 self.shell.system(cmd)
186 self.shell.system(cmd)
187
187
188 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
189 # Main AliasManager class
189 # Main AliasManager class
190 #-----------------------------------------------------------------------------
190 #-----------------------------------------------------------------------------
191
191
192 class AliasManager(Configurable):
192 class AliasManager(Configurable):
193
193
194 default_aliases = List(default_aliases(), config=True)
194 default_aliases = List(default_aliases(), config=True)
195 user_aliases = List(default_value=[], config=True)
195 user_aliases = List(default_value=[], config=True)
196 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
196 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
197
197
198 def __init__(self, shell=None, **kwargs):
198 def __init__(self, shell=None, **kwargs):
199 super(AliasManager, self).__init__(shell=shell, **kwargs)
199 super(AliasManager, self).__init__(shell=shell, **kwargs)
200 # For convenient access
200 # For convenient access
201 self.linemagics = self.shell.magics_manager.magics['line']
201 self.linemagics = self.shell.magics_manager.magics['line']
202 self.init_aliases()
202 self.init_aliases()
203
203
204 def init_aliases(self):
204 def init_aliases(self):
205 # Load default & user aliases
205 # Load default & user aliases
206 for name, cmd in self.default_aliases + self.user_aliases:
206 for name, cmd in self.default_aliases + self.user_aliases:
207 self.soft_define_alias(name, cmd)
207 self.soft_define_alias(name, cmd)
208
208
209 @property
209 @property
210 def aliases(self):
210 def aliases(self):
211 return [(n, func.cmd) for (n, func) in self.linemagics.items()
211 return [(n, func.cmd) for (n, func) in self.linemagics.items()
212 if isinstance(func, Alias)]
212 if isinstance(func, Alias)]
213
213
214 def soft_define_alias(self, name, cmd):
214 def soft_define_alias(self, name, cmd):
215 """Define an alias, but don't raise on an AliasError."""
215 """Define an alias, but don't raise on an AliasError."""
216 try:
216 try:
217 self.define_alias(name, cmd)
217 self.define_alias(name, cmd)
218 except AliasError as e:
218 except AliasError as e:
219 error("Invalid alias: %s" % e)
219 error("Invalid alias: %s" % e)
220
220
221 def define_alias(self, name, cmd):
221 def define_alias(self, name, cmd):
222 """Define a new alias after validating it.
222 """Define a new alias after validating it.
223
223
224 This will raise an :exc:`AliasError` if there are validation
224 This will raise an :exc:`AliasError` if there are validation
225 problems.
225 problems.
226 """
226 """
227 caller = Alias(shell=self.shell, name=name, cmd=cmd)
227 caller = Alias(shell=self.shell, name=name, cmd=cmd)
228 self.shell.magics_manager.register_function(caller, magic_kind='line',
228 self.shell.magics_manager.register_function(caller, magic_kind='line',
229 magic_name=name)
229 magic_name=name)
230
230
231 def get_alias(self, name):
231 def get_alias(self, name):
232 """Return an alias, or None if no alias by that name exists."""
232 """Return an alias, or None if no alias by that name exists."""
233 aname = self.linemagics.get(name, None)
233 aname = self.linemagics.get(name, None)
234 return aname if isinstance(aname, Alias) else None
234 return aname if isinstance(aname, Alias) else None
235
235
236 def is_alias(self, name):
236 def is_alias(self, name):
237 """Return whether or not a given name has been defined as an alias"""
237 """Return whether or not a given name has been defined as an alias"""
238 return self.get_alias(name) is not None
238 return self.get_alias(name) is not None
239
239
240 def undefine_alias(self, name):
240 def undefine_alias(self, name):
241 if self.is_alias(name):
241 if self.is_alias(name):
242 del self.linemagics[name]
242 del self.linemagics[name]
243 else:
243 else:
244 raise ValueError('%s is not an alias' % name)
244 raise ValueError('%s is not an alias' % name)
245
245
246 def clear_aliases(self):
246 def clear_aliases(self):
247 for name, cmd in self.aliases:
247 for name, cmd in self.aliases:
248 self.undefine_alias(name)
248 self.undefine_alias(name)
249
249
250 def retrieve_alias(self, name):
250 def retrieve_alias(self, name):
251 """Retrieve the command to which an alias expands."""
251 """Retrieve the command to which an alias expands."""
252 caller = self.get_alias(name)
252 caller = self.get_alias(name)
253 if caller:
253 if caller:
254 return caller.cmd
254 return caller.cmd
255 else:
255 else:
256 raise ValueError('%s is not an alias' % name)
256 raise ValueError('%s is not an alias' % name)
@@ -1,396 +1,396 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 configurables.
6 handling configuration and creating configurables.
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
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import atexit
15 import atexit
16 import glob
16 import glob
17 import logging
17 import logging
18 import os
18 import os
19 import shutil
19 import shutil
20 import sys
20 import sys
21
21
22 from IPython.config.application import Application, catch_config_error
22 from IPython.config.application import Application, catch_config_error
23 from IPython.config.loader import ConfigFileNotFound, PyFileConfigLoader
23 from IPython.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 from IPython.core import release, crashhandler
24 from IPython.core import release, crashhandler
25 from IPython.core.profiledir import ProfileDir, ProfileDirError
25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
26 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
27 from IPython.utils import py3compat
27 from IPython.utils import py3compat
28 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
28 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
29
29
30 if os.name == 'nt':
30 if os.name == 'nt':
31 programdata = os.environ.get('PROGRAMDATA', None)
31 programdata = os.environ.get('PROGRAMDATA', None)
32 if programdata:
32 if programdata:
33 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
33 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
34 else: # PROGRAMDATA is not defined by default on XP.
34 else: # PROGRAMDATA is not defined by default on XP.
35 SYSTEM_CONFIG_DIRS = []
35 SYSTEM_CONFIG_DIRS = []
36 else:
36 else:
37 SYSTEM_CONFIG_DIRS = [
37 SYSTEM_CONFIG_DIRS = [
38 "/usr/local/etc/ipython",
38 "/usr/local/etc/ipython",
39 "/etc/ipython",
39 "/etc/ipython",
40 ]
40 ]
41
41
42
42
43 # aliases and flags
43 # aliases and flags
44
44
45 base_aliases = {
45 base_aliases = {
46 'profile-dir' : 'ProfileDir.location',
46 'profile-dir' : 'ProfileDir.location',
47 'profile' : 'BaseIPythonApplication.profile',
47 'profile' : 'BaseIPythonApplication.profile',
48 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
48 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
49 'log-level' : 'Application.log_level',
49 'log-level' : 'Application.log_level',
50 'config' : 'BaseIPythonApplication.extra_config_file',
50 'config' : 'BaseIPythonApplication.extra_config_file',
51 }
51 }
52
52
53 base_flags = dict(
53 base_flags = dict(
54 debug = ({'Application' : {'log_level' : logging.DEBUG}},
54 debug = ({'Application' : {'log_level' : logging.DEBUG}},
55 "set log level to logging.DEBUG (maximize logging output)"),
55 "set log level to logging.DEBUG (maximize logging output)"),
56 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
56 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
57 "set log level to logging.CRITICAL (minimize logging output)"),
57 "set log level to logging.CRITICAL (minimize logging output)"),
58 init = ({'BaseIPythonApplication' : {
58 init = ({'BaseIPythonApplication' : {
59 'copy_config_files' : True,
59 'copy_config_files' : True,
60 'auto_create' : True}
60 'auto_create' : True}
61 }, """Initialize profile with default config files. This is equivalent
61 }, """Initialize profile with default config files. This is equivalent
62 to running `ipython profile create <profile>` prior to startup.
62 to running `ipython profile create <profile>` prior to startup.
63 """)
63 """)
64 )
64 )
65
65
66 class ProfileAwareConfigLoader(PyFileConfigLoader):
66 class ProfileAwareConfigLoader(PyFileConfigLoader):
67 """A Python file config loader that is aware of IPython profiles."""
67 """A Python file config loader that is aware of IPython profiles."""
68 def load_subconfig(self, fname, path=None, profile=None):
68 def load_subconfig(self, fname, path=None, profile=None):
69 if profile is not None:
69 if profile is not None:
70 try:
70 try:
71 profile_dir = ProfileDir.find_profile_dir_by_name(
71 profile_dir = ProfileDir.find_profile_dir_by_name(
72 get_ipython_dir(),
72 get_ipython_dir(),
73 profile,
73 profile,
74 )
74 )
75 except ProfileDirError:
75 except ProfileDirError:
76 return
76 return
77 path = profile_dir.location
77 path = profile_dir.location
78 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
78 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
79
79
80 class BaseIPythonApplication(Application):
80 class BaseIPythonApplication(Application):
81
81
82 name = Unicode(u'ipython')
82 name = Unicode(u'ipython')
83 description = Unicode(u'IPython: an enhanced interactive Python shell.')
83 description = Unicode(u'IPython: an enhanced interactive Python shell.')
84 version = Unicode(release.version)
84 version = Unicode(release.version)
85
85
86 aliases = Dict(base_aliases)
86 aliases = Dict(base_aliases)
87 flags = Dict(base_flags)
87 flags = Dict(base_flags)
88 classes = List([ProfileDir])
88 classes = List([ProfileDir])
89
89
90 # enable `load_subconfig('cfg.py', profile='name')`
90 # enable `load_subconfig('cfg.py', profile='name')`
91 python_config_loader_class = ProfileAwareConfigLoader
91 python_config_loader_class = ProfileAwareConfigLoader
92
92
93 # Track whether the config_file has changed,
93 # Track whether the config_file has changed,
94 # because some logic happens only if we aren't using the default.
94 # because some logic happens only if we aren't using the default.
95 config_file_specified = Set()
95 config_file_specified = Set()
96
96
97 config_file_name = Unicode()
97 config_file_name = Unicode()
98 def _config_file_name_default(self):
98 def _config_file_name_default(self):
99 return self.name.replace('-','_') + u'_config.py'
99 return self.name.replace('-','_') + u'_config.py'
100 def _config_file_name_changed(self, name, old, new):
100 def _config_file_name_changed(self, name, old, new):
101 if new != old:
101 if new != old:
102 self.config_file_specified.add(new)
102 self.config_file_specified.add(new)
103
103
104 # The directory that contains IPython's builtin profiles.
104 # The directory that contains IPython's builtin profiles.
105 builtin_profile_dir = Unicode(
105 builtin_profile_dir = Unicode(
106 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
106 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
107 )
107 )
108
108
109 config_file_paths = List(Unicode)
109 config_file_paths = List(Unicode)
110 def _config_file_paths_default(self):
110 def _config_file_paths_default(self):
111 return [py3compat.getcwd()]
111 return [py3compat.getcwd()]
112
112
113 extra_config_file = Unicode(config=True,
113 extra_config_file = Unicode(config=True,
114 help="""Path to an extra config file to load.
114 help="""Path to an extra config file to load.
115
115
116 If specified, load this config file in addition to any other IPython config.
116 If specified, load this config file in addition to any other IPython config.
117 """)
117 """)
118 def _extra_config_file_changed(self, name, old, new):
118 def _extra_config_file_changed(self, name, old, new):
119 try:
119 try:
120 self.config_files.remove(old)
120 self.config_files.remove(old)
121 except ValueError:
121 except ValueError:
122 pass
122 pass
123 self.config_file_specified.add(new)
123 self.config_file_specified.add(new)
124 self.config_files.append(new)
124 self.config_files.append(new)
125
125
126 profile = Unicode(u'default', config=True,
126 profile = Unicode(u'default', config=True,
127 help="""The IPython profile to use."""
127 help="""The IPython profile to use."""
128 )
128 )
129
129
130 def _profile_changed(self, name, old, new):
130 def _profile_changed(self, name, old, new):
131 self.builtin_profile_dir = os.path.join(
131 self.builtin_profile_dir = os.path.join(
132 get_ipython_package_dir(), u'config', u'profile', new
132 get_ipython_package_dir(), u'config', u'profile', new
133 )
133 )
134
134
135 ipython_dir = Unicode(config=True,
135 ipython_dir = Unicode(config=True,
136 help="""
136 help="""
137 The name of the IPython directory. This directory is used for logging
137 The name of the IPython directory. This directory is used for logging
138 configuration (through profiles), history storage, etc. The default
138 configuration (through profiles), history storage, etc. The default
139 is usually $HOME/.ipython. This option can also be specified through
139 is usually $HOME/.ipython. This option can also be specified through
140 the environment variable IPYTHONDIR.
140 the environment variable IPYTHONDIR.
141 """
141 """
142 )
142 )
143 def _ipython_dir_default(self):
143 def _ipython_dir_default(self):
144 d = get_ipython_dir()
144 d = get_ipython_dir()
145 self._ipython_dir_changed('ipython_dir', d, d)
145 self._ipython_dir_changed('ipython_dir', d, d)
146 return d
146 return d
147
147
148 _in_init_profile_dir = False
148 _in_init_profile_dir = False
149 profile_dir = Instance(ProfileDir)
149 profile_dir = Instance(ProfileDir, allow_none=True)
150 def _profile_dir_default(self):
150 def _profile_dir_default(self):
151 # avoid recursion
151 # avoid recursion
152 if self._in_init_profile_dir:
152 if self._in_init_profile_dir:
153 return
153 return
154 # profile_dir requested early, force initialization
154 # profile_dir requested early, force initialization
155 self.init_profile_dir()
155 self.init_profile_dir()
156 return self.profile_dir
156 return self.profile_dir
157
157
158 overwrite = Bool(False, config=True,
158 overwrite = Bool(False, config=True,
159 help="""Whether to overwrite existing config files when copying""")
159 help="""Whether to overwrite existing config files when copying""")
160 auto_create = Bool(False, config=True,
160 auto_create = Bool(False, config=True,
161 help="""Whether to create profile dir if it doesn't exist""")
161 help="""Whether to create profile dir if it doesn't exist""")
162
162
163 config_files = List(Unicode)
163 config_files = List(Unicode)
164 def _config_files_default(self):
164 def _config_files_default(self):
165 return [self.config_file_name]
165 return [self.config_file_name]
166
166
167 copy_config_files = Bool(False, config=True,
167 copy_config_files = Bool(False, config=True,
168 help="""Whether to install the default config files into the profile dir.
168 help="""Whether to install the default config files into the profile dir.
169 If a new profile is being created, and IPython contains config files for that
169 If a new profile is being created, and IPython contains config files for that
170 profile, then they will be staged into the new directory. Otherwise,
170 profile, then they will be staged into the new directory. Otherwise,
171 default config files will be automatically generated.
171 default config files will be automatically generated.
172 """)
172 """)
173
173
174 verbose_crash = Bool(False, config=True,
174 verbose_crash = Bool(False, config=True,
175 help="""Create a massive crash report when IPython encounters what may be an
175 help="""Create a massive crash report when IPython encounters what may be an
176 internal error. The default is to append a short message to the
176 internal error. The default is to append a short message to the
177 usual traceback""")
177 usual traceback""")
178
178
179 # The class to use as the crash handler.
179 # The class to use as the crash handler.
180 crash_handler_class = Type(crashhandler.CrashHandler)
180 crash_handler_class = Type(crashhandler.CrashHandler)
181
181
182 @catch_config_error
182 @catch_config_error
183 def __init__(self, **kwargs):
183 def __init__(self, **kwargs):
184 super(BaseIPythonApplication, self).__init__(**kwargs)
184 super(BaseIPythonApplication, self).__init__(**kwargs)
185 # ensure current working directory exists
185 # ensure current working directory exists
186 try:
186 try:
187 directory = py3compat.getcwd()
187 directory = py3compat.getcwd()
188 except:
188 except:
189 # exit if cwd doesn't exist
189 # exit if cwd doesn't exist
190 self.log.error("Current working directory doesn't exist.")
190 self.log.error("Current working directory doesn't exist.")
191 self.exit(1)
191 self.exit(1)
192
192
193 #-------------------------------------------------------------------------
193 #-------------------------------------------------------------------------
194 # Various stages of Application creation
194 # Various stages of Application creation
195 #-------------------------------------------------------------------------
195 #-------------------------------------------------------------------------
196
196
197 def init_crash_handler(self):
197 def init_crash_handler(self):
198 """Create a crash handler, typically setting sys.excepthook to it."""
198 """Create a crash handler, typically setting sys.excepthook to it."""
199 self.crash_handler = self.crash_handler_class(self)
199 self.crash_handler = self.crash_handler_class(self)
200 sys.excepthook = self.excepthook
200 sys.excepthook = self.excepthook
201 def unset_crashhandler():
201 def unset_crashhandler():
202 sys.excepthook = sys.__excepthook__
202 sys.excepthook = sys.__excepthook__
203 atexit.register(unset_crashhandler)
203 atexit.register(unset_crashhandler)
204
204
205 def excepthook(self, etype, evalue, tb):
205 def excepthook(self, etype, evalue, tb):
206 """this is sys.excepthook after init_crashhandler
206 """this is sys.excepthook after init_crashhandler
207
207
208 set self.verbose_crash=True to use our full crashhandler, instead of
208 set self.verbose_crash=True to use our full crashhandler, instead of
209 a regular traceback with a short message (crash_handler_lite)
209 a regular traceback with a short message (crash_handler_lite)
210 """
210 """
211
211
212 if self.verbose_crash:
212 if self.verbose_crash:
213 return self.crash_handler(etype, evalue, tb)
213 return self.crash_handler(etype, evalue, tb)
214 else:
214 else:
215 return crashhandler.crash_handler_lite(etype, evalue, tb)
215 return crashhandler.crash_handler_lite(etype, evalue, tb)
216
216
217 def _ipython_dir_changed(self, name, old, new):
217 def _ipython_dir_changed(self, name, old, new):
218 if old is not None:
218 if old is not None:
219 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
219 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
220 sys.getfilesystemencoding()
220 sys.getfilesystemencoding()
221 )
221 )
222 if str_old in sys.path:
222 if str_old in sys.path:
223 sys.path.remove(str_old)
223 sys.path.remove(str_old)
224 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
224 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
225 sys.getfilesystemencoding()
225 sys.getfilesystemencoding()
226 )
226 )
227 sys.path.append(str_path)
227 sys.path.append(str_path)
228 ensure_dir_exists(new)
228 ensure_dir_exists(new)
229 readme = os.path.join(new, 'README')
229 readme = os.path.join(new, 'README')
230 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
230 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
231 if not os.path.exists(readme) and os.path.exists(readme_src):
231 if not os.path.exists(readme) and os.path.exists(readme_src):
232 shutil.copy(readme_src, readme)
232 shutil.copy(readme_src, readme)
233 for d in ('extensions', 'nbextensions'):
233 for d in ('extensions', 'nbextensions'):
234 path = os.path.join(new, d)
234 path = os.path.join(new, d)
235 try:
235 try:
236 ensure_dir_exists(path)
236 ensure_dir_exists(path)
237 except OSError:
237 except OSError:
238 # this will not be EEXIST
238 # this will not be EEXIST
239 self.log.error("couldn't create path %s: %s", path, e)
239 self.log.error("couldn't create path %s: %s", path, e)
240 self.log.debug("IPYTHONDIR set to: %s" % new)
240 self.log.debug("IPYTHONDIR set to: %s" % new)
241
241
242 def load_config_file(self, suppress_errors=True):
242 def load_config_file(self, suppress_errors=True):
243 """Load the config file.
243 """Load the config file.
244
244
245 By default, errors in loading config are handled, and a warning
245 By default, errors in loading config are handled, and a warning
246 printed on screen. For testing, the suppress_errors option is set
246 printed on screen. For testing, the suppress_errors option is set
247 to False, so errors will make tests fail.
247 to False, so errors will make tests fail.
248 """
248 """
249 self.log.debug("Searching path %s for config files", self.config_file_paths)
249 self.log.debug("Searching path %s for config files", self.config_file_paths)
250 base_config = 'ipython_config.py'
250 base_config = 'ipython_config.py'
251 self.log.debug("Attempting to load config file: %s" %
251 self.log.debug("Attempting to load config file: %s" %
252 base_config)
252 base_config)
253 try:
253 try:
254 Application.load_config_file(
254 Application.load_config_file(
255 self,
255 self,
256 base_config,
256 base_config,
257 path=self.config_file_paths
257 path=self.config_file_paths
258 )
258 )
259 except ConfigFileNotFound:
259 except ConfigFileNotFound:
260 # ignore errors loading parent
260 # ignore errors loading parent
261 self.log.debug("Config file %s not found", base_config)
261 self.log.debug("Config file %s not found", base_config)
262 pass
262 pass
263
263
264 for config_file_name in self.config_files:
264 for config_file_name in self.config_files:
265 if not config_file_name or config_file_name == base_config:
265 if not config_file_name or config_file_name == base_config:
266 continue
266 continue
267 self.log.debug("Attempting to load config file: %s" %
267 self.log.debug("Attempting to load config file: %s" %
268 self.config_file_name)
268 self.config_file_name)
269 try:
269 try:
270 Application.load_config_file(
270 Application.load_config_file(
271 self,
271 self,
272 config_file_name,
272 config_file_name,
273 path=self.config_file_paths
273 path=self.config_file_paths
274 )
274 )
275 except ConfigFileNotFound:
275 except ConfigFileNotFound:
276 # Only warn if the default config file was NOT being used.
276 # Only warn if the default config file was NOT being used.
277 if config_file_name in self.config_file_specified:
277 if config_file_name in self.config_file_specified:
278 msg = self.log.warn
278 msg = self.log.warn
279 else:
279 else:
280 msg = self.log.debug
280 msg = self.log.debug
281 msg("Config file not found, skipping: %s", config_file_name)
281 msg("Config file not found, skipping: %s", config_file_name)
282 except:
282 except:
283 # For testing purposes.
283 # For testing purposes.
284 if not suppress_errors:
284 if not suppress_errors:
285 raise
285 raise
286 self.log.warn("Error loading config file: %s" %
286 self.log.warn("Error loading config file: %s" %
287 self.config_file_name, exc_info=True)
287 self.config_file_name, exc_info=True)
288
288
289 def init_profile_dir(self):
289 def init_profile_dir(self):
290 """initialize the profile dir"""
290 """initialize the profile dir"""
291 self._in_init_profile_dir = True
291 self._in_init_profile_dir = True
292 if self.profile_dir is not None:
292 if self.profile_dir is not None:
293 # already ran
293 # already ran
294 return
294 return
295 if 'ProfileDir.location' not in self.config:
295 if 'ProfileDir.location' not in self.config:
296 # location not specified, find by profile name
296 # location not specified, find by profile name
297 try:
297 try:
298 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
298 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
299 except ProfileDirError:
299 except ProfileDirError:
300 # not found, maybe create it (always create default profile)
300 # not found, maybe create it (always create default profile)
301 if self.auto_create or self.profile == 'default':
301 if self.auto_create or self.profile == 'default':
302 try:
302 try:
303 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
303 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
304 except ProfileDirError:
304 except ProfileDirError:
305 self.log.fatal("Could not create profile: %r"%self.profile)
305 self.log.fatal("Could not create profile: %r"%self.profile)
306 self.exit(1)
306 self.exit(1)
307 else:
307 else:
308 self.log.info("Created profile dir: %r"%p.location)
308 self.log.info("Created profile dir: %r"%p.location)
309 else:
309 else:
310 self.log.fatal("Profile %r not found."%self.profile)
310 self.log.fatal("Profile %r not found."%self.profile)
311 self.exit(1)
311 self.exit(1)
312 else:
312 else:
313 self.log.debug("Using existing profile dir: %r"%p.location)
313 self.log.debug("Using existing profile dir: %r"%p.location)
314 else:
314 else:
315 location = self.config.ProfileDir.location
315 location = self.config.ProfileDir.location
316 # location is fully specified
316 # location is fully specified
317 try:
317 try:
318 p = ProfileDir.find_profile_dir(location, self.config)
318 p = ProfileDir.find_profile_dir(location, self.config)
319 except ProfileDirError:
319 except ProfileDirError:
320 # not found, maybe create it
320 # not found, maybe create it
321 if self.auto_create:
321 if self.auto_create:
322 try:
322 try:
323 p = ProfileDir.create_profile_dir(location, self.config)
323 p = ProfileDir.create_profile_dir(location, self.config)
324 except ProfileDirError:
324 except ProfileDirError:
325 self.log.fatal("Could not create profile directory: %r"%location)
325 self.log.fatal("Could not create profile directory: %r"%location)
326 self.exit(1)
326 self.exit(1)
327 else:
327 else:
328 self.log.debug("Creating new profile dir: %r"%location)
328 self.log.debug("Creating new profile dir: %r"%location)
329 else:
329 else:
330 self.log.fatal("Profile directory %r not found."%location)
330 self.log.fatal("Profile directory %r not found."%location)
331 self.exit(1)
331 self.exit(1)
332 else:
332 else:
333 self.log.info("Using existing profile dir: %r"%location)
333 self.log.info("Using existing profile dir: %r"%location)
334 # if profile_dir is specified explicitly, set profile name
334 # if profile_dir is specified explicitly, set profile name
335 dir_name = os.path.basename(p.location)
335 dir_name = os.path.basename(p.location)
336 if dir_name.startswith('profile_'):
336 if dir_name.startswith('profile_'):
337 self.profile = dir_name[8:]
337 self.profile = dir_name[8:]
338
338
339 self.profile_dir = p
339 self.profile_dir = p
340 self.config_file_paths.append(p.location)
340 self.config_file_paths.append(p.location)
341 self._in_init_profile_dir = False
341 self._in_init_profile_dir = False
342
342
343 def init_config_files(self):
343 def init_config_files(self):
344 """[optionally] copy default config files into profile dir."""
344 """[optionally] copy default config files into profile dir."""
345 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
345 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
346 # copy config files
346 # copy config files
347 path = self.builtin_profile_dir
347 path = self.builtin_profile_dir
348 if self.copy_config_files:
348 if self.copy_config_files:
349 src = self.profile
349 src = self.profile
350
350
351 cfg = self.config_file_name
351 cfg = self.config_file_name
352 if path and os.path.exists(os.path.join(path, cfg)):
352 if path and os.path.exists(os.path.join(path, cfg)):
353 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
353 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
354 cfg, src, self.profile_dir.location, self.overwrite)
354 cfg, src, self.profile_dir.location, self.overwrite)
355 )
355 )
356 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
356 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
357 else:
357 else:
358 self.stage_default_config_file()
358 self.stage_default_config_file()
359 else:
359 else:
360 # Still stage *bundled* config files, but not generated ones
360 # Still stage *bundled* config files, but not generated ones
361 # This is necessary for `ipython profile=sympy` to load the profile
361 # This is necessary for `ipython profile=sympy` to load the profile
362 # on the first go
362 # on the first go
363 files = glob.glob(os.path.join(path, '*.py'))
363 files = glob.glob(os.path.join(path, '*.py'))
364 for fullpath in files:
364 for fullpath in files:
365 cfg = os.path.basename(fullpath)
365 cfg = os.path.basename(fullpath)
366 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
366 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
367 # file was copied
367 # file was copied
368 self.log.warn("Staging bundled %s from %s into %r"%(
368 self.log.warn("Staging bundled %s from %s into %r"%(
369 cfg, self.profile, self.profile_dir.location)
369 cfg, self.profile, self.profile_dir.location)
370 )
370 )
371
371
372
372
373 def stage_default_config_file(self):
373 def stage_default_config_file(self):
374 """auto generate default config file, and stage it into the profile."""
374 """auto generate default config file, and stage it into the profile."""
375 s = self.generate_config_file()
375 s = self.generate_config_file()
376 fname = os.path.join(self.profile_dir.location, self.config_file_name)
376 fname = os.path.join(self.profile_dir.location, self.config_file_name)
377 if self.overwrite or not os.path.exists(fname):
377 if self.overwrite or not os.path.exists(fname):
378 self.log.warn("Generating default config file: %r"%(fname))
378 self.log.warn("Generating default config file: %r"%(fname))
379 with open(fname, 'w') as f:
379 with open(fname, 'w') as f:
380 f.write(s)
380 f.write(s)
381
381
382 @catch_config_error
382 @catch_config_error
383 def initialize(self, argv=None):
383 def initialize(self, argv=None):
384 # don't hook up crash handler before parsing command-line
384 # don't hook up crash handler before parsing command-line
385 self.parse_command_line(argv)
385 self.parse_command_line(argv)
386 self.init_crash_handler()
386 self.init_crash_handler()
387 if self.subapp is not None:
387 if self.subapp is not None:
388 # stop here if subapp is taking over
388 # stop here if subapp is taking over
389 return
389 return
390 cl_config = self.config
390 cl_config = self.config
391 self.init_profile_dir()
391 self.init_profile_dir()
392 self.init_config_files()
392 self.init_config_files()
393 self.load_config_file()
393 self.load_config_file()
394 # enforce cl-opts override configfile opts:
394 # enforce cl-opts override configfile opts:
395 self.update_config(cl_config)
395 self.update_config(cl_config)
396
396
@@ -1,111 +1,112 b''
1 """
1 """
2 A context manager for managing things injected into :mod:`__builtin__`.
2 A context manager for managing things injected into :mod:`__builtin__`.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 * Fernando Perez
7 * Fernando Perez
8 """
8 """
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2010-2011 The IPython Development Team.
10 # Copyright (C) 2010-2011 The IPython Development Team.
11 #
11 #
12 # Distributed under the terms of the BSD License.
12 # Distributed under the terms of the BSD License.
13 #
13 #
14 # Complete license in the file COPYING.txt, distributed with this software.
14 # Complete license in the file COPYING.txt, distributed with this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from IPython.config.configurable import Configurable
21 from IPython.config.configurable import Configurable
22
22
23 from IPython.utils.py3compat import builtin_mod, iteritems
23 from IPython.utils.py3compat import builtin_mod, iteritems
24 from IPython.utils.traitlets import Instance
24 from IPython.utils.traitlets import Instance
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Classes and functions
27 # Classes and functions
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 class __BuiltinUndefined(object): pass
30 class __BuiltinUndefined(object): pass
31 BuiltinUndefined = __BuiltinUndefined()
31 BuiltinUndefined = __BuiltinUndefined()
32
32
33 class __HideBuiltin(object): pass
33 class __HideBuiltin(object): pass
34 HideBuiltin = __HideBuiltin()
34 HideBuiltin = __HideBuiltin()
35
35
36
36
37 class BuiltinTrap(Configurable):
37 class BuiltinTrap(Configurable):
38
38
39 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
39 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
40 allow_none=True)
40
41
41 def __init__(self, shell=None):
42 def __init__(self, shell=None):
42 super(BuiltinTrap, self).__init__(shell=shell, config=None)
43 super(BuiltinTrap, self).__init__(shell=shell, config=None)
43 self._orig_builtins = {}
44 self._orig_builtins = {}
44 # We define this to track if a single BuiltinTrap is nested.
45 # We define this to track if a single BuiltinTrap is nested.
45 # Only turn off the trap when the outermost call to __exit__ is made.
46 # Only turn off the trap when the outermost call to __exit__ is made.
46 self._nested_level = 0
47 self._nested_level = 0
47 self.shell = shell
48 self.shell = shell
48 # builtins we always add - if set to HideBuiltin, they will just
49 # builtins we always add - if set to HideBuiltin, they will just
49 # be removed instead of being replaced by something else
50 # be removed instead of being replaced by something else
50 self.auto_builtins = {'exit': HideBuiltin,
51 self.auto_builtins = {'exit': HideBuiltin,
51 'quit': HideBuiltin,
52 'quit': HideBuiltin,
52 'get_ipython': self.shell.get_ipython,
53 'get_ipython': self.shell.get_ipython,
53 }
54 }
54 # Recursive reload function
55 # Recursive reload function
55 try:
56 try:
56 from IPython.lib import deepreload
57 from IPython.lib import deepreload
57 if self.shell.deep_reload:
58 if self.shell.deep_reload:
58 self.auto_builtins['reload'] = deepreload.reload
59 self.auto_builtins['reload'] = deepreload.reload
59 else:
60 else:
60 self.auto_builtins['dreload']= deepreload.reload
61 self.auto_builtins['dreload']= deepreload.reload
61 except ImportError:
62 except ImportError:
62 pass
63 pass
63
64
64 def __enter__(self):
65 def __enter__(self):
65 if self._nested_level == 0:
66 if self._nested_level == 0:
66 self.activate()
67 self.activate()
67 self._nested_level += 1
68 self._nested_level += 1
68 # I return self, so callers can use add_builtin in a with clause.
69 # I return self, so callers can use add_builtin in a with clause.
69 return self
70 return self
70
71
71 def __exit__(self, type, value, traceback):
72 def __exit__(self, type, value, traceback):
72 if self._nested_level == 1:
73 if self._nested_level == 1:
73 self.deactivate()
74 self.deactivate()
74 self._nested_level -= 1
75 self._nested_level -= 1
75 # Returning False will cause exceptions to propagate
76 # Returning False will cause exceptions to propagate
76 return False
77 return False
77
78
78 def add_builtin(self, key, value):
79 def add_builtin(self, key, value):
79 """Add a builtin and save the original."""
80 """Add a builtin and save the original."""
80 bdict = builtin_mod.__dict__
81 bdict = builtin_mod.__dict__
81 orig = bdict.get(key, BuiltinUndefined)
82 orig = bdict.get(key, BuiltinUndefined)
82 if value is HideBuiltin:
83 if value is HideBuiltin:
83 if orig is not BuiltinUndefined: #same as 'key in bdict'
84 if orig is not BuiltinUndefined: #same as 'key in bdict'
84 self._orig_builtins[key] = orig
85 self._orig_builtins[key] = orig
85 del bdict[key]
86 del bdict[key]
86 else:
87 else:
87 self._orig_builtins[key] = orig
88 self._orig_builtins[key] = orig
88 bdict[key] = value
89 bdict[key] = value
89
90
90 def remove_builtin(self, key, orig):
91 def remove_builtin(self, key, orig):
91 """Remove an added builtin and re-set the original."""
92 """Remove an added builtin and re-set the original."""
92 if orig is BuiltinUndefined:
93 if orig is BuiltinUndefined:
93 del builtin_mod.__dict__[key]
94 del builtin_mod.__dict__[key]
94 else:
95 else:
95 builtin_mod.__dict__[key] = orig
96 builtin_mod.__dict__[key] = orig
96
97
97 def activate(self):
98 def activate(self):
98 """Store ipython references in the __builtin__ namespace."""
99 """Store ipython references in the __builtin__ namespace."""
99
100
100 add_builtin = self.add_builtin
101 add_builtin = self.add_builtin
101 for name, func in iteritems(self.auto_builtins):
102 for name, func in iteritems(self.auto_builtins):
102 add_builtin(name, func)
103 add_builtin(name, func)
103
104
104 def deactivate(self):
105 def deactivate(self):
105 """Remove any builtins which might have been added by add_builtins, or
106 """Remove any builtins which might have been added by add_builtins, or
106 restore overwritten ones to their previous values."""
107 restore overwritten ones to their previous values."""
107 remove_builtin = self.remove_builtin
108 remove_builtin = self.remove_builtin
108 for key, val in iteritems(self._orig_builtins):
109 for key, val in iteritems(self._orig_builtins):
109 remove_builtin(key, val)
110 remove_builtin(key, val)
110 self._orig_builtins.clear()
111 self._orig_builtins.clear()
111 self._builtins_added = False
112 self._builtins_added = False
@@ -1,282 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Displayhook for IPython.
2 """Displayhook for IPython.
3
3
4 This defines a callable class that IPython uses for `sys.displayhook`.
4 This defines a callable class that IPython uses for `sys.displayhook`.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 import sys
12 import sys
13
13
14 from IPython.core.formatters import _safe_get_formatter_method
14 from IPython.core.formatters import _safe_get_formatter_method
15 from IPython.config.configurable import Configurable
15 from IPython.config.configurable import Configurable
16 from IPython.utils import io
16 from IPython.utils import io
17 from IPython.utils.py3compat import builtin_mod
17 from IPython.utils.py3compat import builtin_mod
18 from IPython.utils.traitlets import Instance, Float
18 from IPython.utils.traitlets import Instance, Float
19 from IPython.utils.warn import warn
19 from IPython.utils.warn import warn
20
20
21 # TODO: Move the various attributes (cache_size, [others now moved]). Some
21 # TODO: Move the various attributes (cache_size, [others now moved]). Some
22 # of these are also attributes of InteractiveShell. They should be on ONE object
22 # of these are also attributes of InteractiveShell. They should be on ONE object
23 # only and the other objects should ask that one object for their values.
23 # only and the other objects should ask that one object for their values.
24
24
25 class DisplayHook(Configurable):
25 class DisplayHook(Configurable):
26 """The custom IPython displayhook to replace sys.displayhook.
26 """The custom IPython displayhook to replace sys.displayhook.
27
27
28 This class does many things, but the basic idea is that it is a callable
28 This class does many things, but the basic idea is that it is a callable
29 that gets called anytime user code returns a value.
29 that gets called anytime user code returns a value.
30 """
30 """
31
31
32 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
32 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
33 allow_none=True)
33 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
34 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
34 allow_none=True)
35 allow_none=True)
35 cull_fraction = Float(0.2)
36 cull_fraction = Float(0.2)
36
37
37 def __init__(self, shell=None, cache_size=1000, **kwargs):
38 def __init__(self, shell=None, cache_size=1000, **kwargs):
38 super(DisplayHook, self).__init__(shell=shell, **kwargs)
39 super(DisplayHook, self).__init__(shell=shell, **kwargs)
39 cache_size_min = 3
40 cache_size_min = 3
40 if cache_size <= 0:
41 if cache_size <= 0:
41 self.do_full_cache = 0
42 self.do_full_cache = 0
42 cache_size = 0
43 cache_size = 0
43 elif cache_size < cache_size_min:
44 elif cache_size < cache_size_min:
44 self.do_full_cache = 0
45 self.do_full_cache = 0
45 cache_size = 0
46 cache_size = 0
46 warn('caching was disabled (min value for cache size is %s).' %
47 warn('caching was disabled (min value for cache size is %s).' %
47 cache_size_min,level=3)
48 cache_size_min,level=3)
48 else:
49 else:
49 self.do_full_cache = 1
50 self.do_full_cache = 1
50
51
51 self.cache_size = cache_size
52 self.cache_size = cache_size
52
53
53 # we need a reference to the user-level namespace
54 # we need a reference to the user-level namespace
54 self.shell = shell
55 self.shell = shell
55
56
56 self._,self.__,self.___ = '','',''
57 self._,self.__,self.___ = '','',''
57
58
58 # these are deliberately global:
59 # these are deliberately global:
59 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
60 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
60 self.shell.user_ns.update(to_user_ns)
61 self.shell.user_ns.update(to_user_ns)
61
62
62 @property
63 @property
63 def prompt_count(self):
64 def prompt_count(self):
64 return self.shell.execution_count
65 return self.shell.execution_count
65
66
66 #-------------------------------------------------------------------------
67 #-------------------------------------------------------------------------
67 # Methods used in __call__. Override these methods to modify the behavior
68 # Methods used in __call__. Override these methods to modify the behavior
68 # of the displayhook.
69 # of the displayhook.
69 #-------------------------------------------------------------------------
70 #-------------------------------------------------------------------------
70
71
71 def check_for_underscore(self):
72 def check_for_underscore(self):
72 """Check if the user has set the '_' variable by hand."""
73 """Check if the user has set the '_' variable by hand."""
73 # If something injected a '_' variable in __builtin__, delete
74 # If something injected a '_' variable in __builtin__, delete
74 # ipython's automatic one so we don't clobber that. gettext() in
75 # ipython's automatic one so we don't clobber that. gettext() in
75 # particular uses _, so we need to stay away from it.
76 # particular uses _, so we need to stay away from it.
76 if '_' in builtin_mod.__dict__:
77 if '_' in builtin_mod.__dict__:
77 try:
78 try:
78 del self.shell.user_ns['_']
79 del self.shell.user_ns['_']
79 except KeyError:
80 except KeyError:
80 pass
81 pass
81
82
82 def quiet(self):
83 def quiet(self):
83 """Should we silence the display hook because of ';'?"""
84 """Should we silence the display hook because of ';'?"""
84 # do not print output if input ends in ';'
85 # do not print output if input ends in ';'
85 try:
86 try:
86 cell = self.shell.history_manager.input_hist_parsed[self.prompt_count]
87 cell = self.shell.history_manager.input_hist_parsed[self.prompt_count]
87 return cell.rstrip().endswith(';')
88 return cell.rstrip().endswith(';')
88 except IndexError:
89 except IndexError:
89 # some uses of ipshellembed may fail here
90 # some uses of ipshellembed may fail here
90 return False
91 return False
91
92
92 def start_displayhook(self):
93 def start_displayhook(self):
93 """Start the displayhook, initializing resources."""
94 """Start the displayhook, initializing resources."""
94 pass
95 pass
95
96
96 def write_output_prompt(self):
97 def write_output_prompt(self):
97 """Write the output prompt.
98 """Write the output prompt.
98
99
99 The default implementation simply writes the prompt to
100 The default implementation simply writes the prompt to
100 ``io.stdout``.
101 ``io.stdout``.
101 """
102 """
102 # Use write, not print which adds an extra space.
103 # Use write, not print which adds an extra space.
103 io.stdout.write(self.shell.separate_out)
104 io.stdout.write(self.shell.separate_out)
104 outprompt = self.shell.prompt_manager.render('out')
105 outprompt = self.shell.prompt_manager.render('out')
105 if self.do_full_cache:
106 if self.do_full_cache:
106 io.stdout.write(outprompt)
107 io.stdout.write(outprompt)
107
108
108 def compute_format_data(self, result):
109 def compute_format_data(self, result):
109 """Compute format data of the object to be displayed.
110 """Compute format data of the object to be displayed.
110
111
111 The format data is a generalization of the :func:`repr` of an object.
112 The format data is a generalization of the :func:`repr` of an object.
112 In the default implementation the format data is a :class:`dict` of
113 In the default implementation the format data is a :class:`dict` of
113 key value pair where the keys are valid MIME types and the values
114 key value pair where the keys are valid MIME types and the values
114 are JSON'able data structure containing the raw data for that MIME
115 are JSON'able data structure containing the raw data for that MIME
115 type. It is up to frontends to determine pick a MIME to to use and
116 type. It is up to frontends to determine pick a MIME to to use and
116 display that data in an appropriate manner.
117 display that data in an appropriate manner.
117
118
118 This method only computes the format data for the object and should
119 This method only computes the format data for the object and should
119 NOT actually print or write that to a stream.
120 NOT actually print or write that to a stream.
120
121
121 Parameters
122 Parameters
122 ----------
123 ----------
123 result : object
124 result : object
124 The Python object passed to the display hook, whose format will be
125 The Python object passed to the display hook, whose format will be
125 computed.
126 computed.
126
127
127 Returns
128 Returns
128 -------
129 -------
129 (format_dict, md_dict) : dict
130 (format_dict, md_dict) : dict
130 format_dict is a :class:`dict` whose keys are valid MIME types and values are
131 format_dict is a :class:`dict` whose keys are valid MIME types and values are
131 JSON'able raw data for that MIME type. It is recommended that
132 JSON'able raw data for that MIME type. It is recommended that
132 all return values of this should always include the "text/plain"
133 all return values of this should always include the "text/plain"
133 MIME type representation of the object.
134 MIME type representation of the object.
134 md_dict is a :class:`dict` with the same MIME type keys
135 md_dict is a :class:`dict` with the same MIME type keys
135 of metadata associated with each output.
136 of metadata associated with each output.
136
137
137 """
138 """
138 return self.shell.display_formatter.format(result)
139 return self.shell.display_formatter.format(result)
139
140
140 def write_format_data(self, format_dict, md_dict=None):
141 def write_format_data(self, format_dict, md_dict=None):
141 """Write the format data dict to the frontend.
142 """Write the format data dict to the frontend.
142
143
143 This default version of this method simply writes the plain text
144 This default version of this method simply writes the plain text
144 representation of the object to ``io.stdout``. Subclasses should
145 representation of the object to ``io.stdout``. Subclasses should
145 override this method to send the entire `format_dict` to the
146 override this method to send the entire `format_dict` to the
146 frontends.
147 frontends.
147
148
148 Parameters
149 Parameters
149 ----------
150 ----------
150 format_dict : dict
151 format_dict : dict
151 The format dict for the object passed to `sys.displayhook`.
152 The format dict for the object passed to `sys.displayhook`.
152 md_dict : dict (optional)
153 md_dict : dict (optional)
153 The metadata dict to be associated with the display data.
154 The metadata dict to be associated with the display data.
154 """
155 """
155 if 'text/plain' not in format_dict:
156 if 'text/plain' not in format_dict:
156 # nothing to do
157 # nothing to do
157 return
158 return
158 # We want to print because we want to always make sure we have a
159 # We want to print because we want to always make sure we have a
159 # newline, even if all the prompt separators are ''. This is the
160 # newline, even if all the prompt separators are ''. This is the
160 # standard IPython behavior.
161 # standard IPython behavior.
161 result_repr = format_dict['text/plain']
162 result_repr = format_dict['text/plain']
162 if '\n' in result_repr:
163 if '\n' in result_repr:
163 # So that multi-line strings line up with the left column of
164 # So that multi-line strings line up with the left column of
164 # the screen, instead of having the output prompt mess up
165 # the screen, instead of having the output prompt mess up
165 # their first line.
166 # their first line.
166 # We use the prompt template instead of the expanded prompt
167 # We use the prompt template instead of the expanded prompt
167 # because the expansion may add ANSI escapes that will interfere
168 # because the expansion may add ANSI escapes that will interfere
168 # with our ability to determine whether or not we should add
169 # with our ability to determine whether or not we should add
169 # a newline.
170 # a newline.
170 prompt_template = self.shell.prompt_manager.out_template
171 prompt_template = self.shell.prompt_manager.out_template
171 if prompt_template and not prompt_template.endswith('\n'):
172 if prompt_template and not prompt_template.endswith('\n'):
172 # But avoid extraneous empty lines.
173 # But avoid extraneous empty lines.
173 result_repr = '\n' + result_repr
174 result_repr = '\n' + result_repr
174
175
175 print(result_repr, file=io.stdout)
176 print(result_repr, file=io.stdout)
176
177
177 def update_user_ns(self, result):
178 def update_user_ns(self, result):
178 """Update user_ns with various things like _, __, _1, etc."""
179 """Update user_ns with various things like _, __, _1, etc."""
179
180
180 # Avoid recursive reference when displaying _oh/Out
181 # Avoid recursive reference when displaying _oh/Out
181 if result is not self.shell.user_ns['_oh']:
182 if result is not self.shell.user_ns['_oh']:
182 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
183 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
183 self.cull_cache()
184 self.cull_cache()
184 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
185 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
185 # we cause buggy behavior for things like gettext).
186 # we cause buggy behavior for things like gettext).
186
187
187 if '_' not in builtin_mod.__dict__:
188 if '_' not in builtin_mod.__dict__:
188 self.___ = self.__
189 self.___ = self.__
189 self.__ = self._
190 self.__ = self._
190 self._ = result
191 self._ = result
191 self.shell.push({'_':self._,
192 self.shell.push({'_':self._,
192 '__':self.__,
193 '__':self.__,
193 '___':self.___}, interactive=False)
194 '___':self.___}, interactive=False)
194
195
195 # hackish access to top-level namespace to create _1,_2... dynamically
196 # hackish access to top-level namespace to create _1,_2... dynamically
196 to_main = {}
197 to_main = {}
197 if self.do_full_cache:
198 if self.do_full_cache:
198 new_result = '_'+repr(self.prompt_count)
199 new_result = '_'+repr(self.prompt_count)
199 to_main[new_result] = result
200 to_main[new_result] = result
200 self.shell.push(to_main, interactive=False)
201 self.shell.push(to_main, interactive=False)
201 self.shell.user_ns['_oh'][self.prompt_count] = result
202 self.shell.user_ns['_oh'][self.prompt_count] = result
202
203
203 def fill_exec_result(self, result):
204 def fill_exec_result(self, result):
204 if self.exec_result is not None:
205 if self.exec_result is not None:
205 self.exec_result.result = result
206 self.exec_result.result = result
206
207
207 def log_output(self, format_dict):
208 def log_output(self, format_dict):
208 """Log the output."""
209 """Log the output."""
209 if 'text/plain' not in format_dict:
210 if 'text/plain' not in format_dict:
210 # nothing to do
211 # nothing to do
211 return
212 return
212 if self.shell.logger.log_output:
213 if self.shell.logger.log_output:
213 self.shell.logger.log_write(format_dict['text/plain'], 'output')
214 self.shell.logger.log_write(format_dict['text/plain'], 'output')
214 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
215 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
215 format_dict['text/plain']
216 format_dict['text/plain']
216
217
217 def finish_displayhook(self):
218 def finish_displayhook(self):
218 """Finish up all displayhook activities."""
219 """Finish up all displayhook activities."""
219 io.stdout.write(self.shell.separate_out2)
220 io.stdout.write(self.shell.separate_out2)
220 io.stdout.flush()
221 io.stdout.flush()
221
222
222 def __call__(self, result=None):
223 def __call__(self, result=None):
223 """Printing with history cache management.
224 """Printing with history cache management.
224
225
225 This is invoked everytime the interpreter needs to print, and is
226 This is invoked everytime the interpreter needs to print, and is
226 activated by setting the variable sys.displayhook to it.
227 activated by setting the variable sys.displayhook to it.
227 """
228 """
228 self.check_for_underscore()
229 self.check_for_underscore()
229 if result is not None and not self.quiet():
230 if result is not None and not self.quiet():
230 self.start_displayhook()
231 self.start_displayhook()
231 self.write_output_prompt()
232 self.write_output_prompt()
232 format_dict, md_dict = self.compute_format_data(result)
233 format_dict, md_dict = self.compute_format_data(result)
233 self.update_user_ns(result)
234 self.update_user_ns(result)
234 self.fill_exec_result(result)
235 self.fill_exec_result(result)
235 if format_dict:
236 if format_dict:
236 self.write_format_data(format_dict, md_dict)
237 self.write_format_data(format_dict, md_dict)
237 self.log_output(format_dict)
238 self.log_output(format_dict)
238 self.finish_displayhook()
239 self.finish_displayhook()
239
240
240 def cull_cache(self):
241 def cull_cache(self):
241 """Output cache is full, cull the oldest entries"""
242 """Output cache is full, cull the oldest entries"""
242 oh = self.shell.user_ns.get('_oh', {})
243 oh = self.shell.user_ns.get('_oh', {})
243 sz = len(oh)
244 sz = len(oh)
244 cull_count = max(int(sz * self.cull_fraction), 2)
245 cull_count = max(int(sz * self.cull_fraction), 2)
245 warn('Output cache limit (currently {sz} entries) hit.\n'
246 warn('Output cache limit (currently {sz} entries) hit.\n'
246 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
247 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
247
248
248 for i, n in enumerate(sorted(oh)):
249 for i, n in enumerate(sorted(oh)):
249 if i >= cull_count:
250 if i >= cull_count:
250 break
251 break
251 self.shell.user_ns.pop('_%i' % n, None)
252 self.shell.user_ns.pop('_%i' % n, None)
252 oh.pop(n, None)
253 oh.pop(n, None)
253
254
254
255
255 def flush(self):
256 def flush(self):
256 if not self.do_full_cache:
257 if not self.do_full_cache:
257 raise ValueError("You shouldn't have reached the cache flush "
258 raise ValueError("You shouldn't have reached the cache flush "
258 "if full caching is not enabled!")
259 "if full caching is not enabled!")
259 # delete auto-generated vars from global namespace
260 # delete auto-generated vars from global namespace
260
261
261 for n in range(1,self.prompt_count + 1):
262 for n in range(1,self.prompt_count + 1):
262 key = '_'+repr(n)
263 key = '_'+repr(n)
263 try:
264 try:
264 del self.shell.user_ns[key]
265 del self.shell.user_ns[key]
265 except: pass
266 except: pass
266 # In some embedded circumstances, the user_ns doesn't have the
267 # In some embedded circumstances, the user_ns doesn't have the
267 # '_oh' key set up.
268 # '_oh' key set up.
268 oh = self.shell.user_ns.get('_oh', None)
269 oh = self.shell.user_ns.get('_oh', None)
269 if oh is not None:
270 if oh is not None:
270 oh.clear()
271 oh.clear()
271
272
272 # Release our own references to objects:
273 # Release our own references to objects:
273 self._, self.__, self.___ = '', '', ''
274 self._, self.__, self.___ = '', '', ''
274
275
275 if '_' not in builtin_mod.__dict__:
276 if '_' not in builtin_mod.__dict__:
276 self.shell.user_ns.update({'_':None,'__':None, '___':None})
277 self.shell.user_ns.update({'_':None,'__':None, '___':None})
277 import gc
278 import gc
278 # TODO: Is this really needed?
279 # TODO: Is this really needed?
279 # IronPython blocks here forever
280 # IronPython blocks here forever
280 if sys.platform != "cli":
281 if sys.platform != "cli":
281 gc.collect()
282 gc.collect()
282
283
@@ -1,175 +1,176 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """A class for managing IPython extensions."""
2 """A class for managing IPython extensions."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 from shutil import copyfile
8 from shutil import copyfile
9 import sys
9 import sys
10
10
11 from IPython.config.configurable import Configurable
11 from IPython.config.configurable import Configurable
12 from IPython.utils.path import ensure_dir_exists
12 from IPython.utils.path import ensure_dir_exists
13 from IPython.utils.traitlets import Instance
13 from IPython.utils.traitlets import Instance
14 from IPython.utils.py3compat import PY3
14 from IPython.utils.py3compat import PY3
15 if PY3:
15 if PY3:
16 from imp import reload
16 from imp import reload
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Main class
19 # Main class
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 class ExtensionManager(Configurable):
22 class ExtensionManager(Configurable):
23 """A class to manage IPython extensions.
23 """A class to manage IPython extensions.
24
24
25 An IPython extension is an importable Python module that has
25 An IPython extension is an importable Python module that has
26 a function with the signature::
26 a function with the signature::
27
27
28 def load_ipython_extension(ipython):
28 def load_ipython_extension(ipython):
29 # Do things with ipython
29 # Do things with ipython
30
30
31 This function is called after your extension is imported and the
31 This function is called after your extension is imported and the
32 currently active :class:`InteractiveShell` instance is passed as
32 currently active :class:`InteractiveShell` instance is passed as
33 the only argument. You can do anything you want with IPython at
33 the only argument. You can do anything you want with IPython at
34 that point, including defining new magic and aliases, adding new
34 that point, including defining new magic and aliases, adding new
35 components, etc.
35 components, etc.
36
36
37 You can also optionally define an :func:`unload_ipython_extension(ipython)`
37 You can also optionally define an :func:`unload_ipython_extension(ipython)`
38 function, which will be called if the user unloads or reloads the extension.
38 function, which will be called if the user unloads or reloads the extension.
39 The extension manager will only call :func:`load_ipython_extension` again
39 The extension manager will only call :func:`load_ipython_extension` again
40 if the extension is reloaded.
40 if the extension is reloaded.
41
41
42 You can put your extension modules anywhere you want, as long as
42 You can put your extension modules anywhere you want, as long as
43 they can be imported by Python's standard import mechanism. However,
43 they can be imported by Python's standard import mechanism. However,
44 to make it easy to write extensions, you can also put your extensions
44 to make it easy to write extensions, you can also put your extensions
45 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
45 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
46 is added to ``sys.path`` automatically.
46 is added to ``sys.path`` automatically.
47 """
47 """
48
48
49 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
49 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
50 allow_none=True)
50
51
51 def __init__(self, shell=None, **kwargs):
52 def __init__(self, shell=None, **kwargs):
52 super(ExtensionManager, self).__init__(shell=shell, **kwargs)
53 super(ExtensionManager, self).__init__(shell=shell, **kwargs)
53 self.shell.on_trait_change(
54 self.shell.on_trait_change(
54 self._on_ipython_dir_changed, 'ipython_dir'
55 self._on_ipython_dir_changed, 'ipython_dir'
55 )
56 )
56 self.loaded = set()
57 self.loaded = set()
57
58
58 def __del__(self):
59 def __del__(self):
59 self.shell.on_trait_change(
60 self.shell.on_trait_change(
60 self._on_ipython_dir_changed, 'ipython_dir', remove=True
61 self._on_ipython_dir_changed, 'ipython_dir', remove=True
61 )
62 )
62
63
63 @property
64 @property
64 def ipython_extension_dir(self):
65 def ipython_extension_dir(self):
65 return os.path.join(self.shell.ipython_dir, u'extensions')
66 return os.path.join(self.shell.ipython_dir, u'extensions')
66
67
67 def _on_ipython_dir_changed(self):
68 def _on_ipython_dir_changed(self):
68 ensure_dir_exists(self.ipython_extension_dir)
69 ensure_dir_exists(self.ipython_extension_dir)
69
70
70 def load_extension(self, module_str):
71 def load_extension(self, module_str):
71 """Load an IPython extension by its module name.
72 """Load an IPython extension by its module name.
72
73
73 Returns the string "already loaded" if the extension is already loaded,
74 Returns the string "already loaded" if the extension is already loaded,
74 "no load function" if the module doesn't have a load_ipython_extension
75 "no load function" if the module doesn't have a load_ipython_extension
75 function, or None if it succeeded.
76 function, or None if it succeeded.
76 """
77 """
77 if module_str in self.loaded:
78 if module_str in self.loaded:
78 return "already loaded"
79 return "already loaded"
79
80
80 from IPython.utils.syspathcontext import prepended_to_syspath
81 from IPython.utils.syspathcontext import prepended_to_syspath
81
82
82 with self.shell.builtin_trap:
83 with self.shell.builtin_trap:
83 if module_str not in sys.modules:
84 if module_str not in sys.modules:
84 with prepended_to_syspath(self.ipython_extension_dir):
85 with prepended_to_syspath(self.ipython_extension_dir):
85 __import__(module_str)
86 __import__(module_str)
86 mod = sys.modules[module_str]
87 mod = sys.modules[module_str]
87 if self._call_load_ipython_extension(mod):
88 if self._call_load_ipython_extension(mod):
88 self.loaded.add(module_str)
89 self.loaded.add(module_str)
89 else:
90 else:
90 return "no load function"
91 return "no load function"
91
92
92 def unload_extension(self, module_str):
93 def unload_extension(self, module_str):
93 """Unload an IPython extension by its module name.
94 """Unload an IPython extension by its module name.
94
95
95 This function looks up the extension's name in ``sys.modules`` and
96 This function looks up the extension's name in ``sys.modules`` and
96 simply calls ``mod.unload_ipython_extension(self)``.
97 simply calls ``mod.unload_ipython_extension(self)``.
97
98
98 Returns the string "no unload function" if the extension doesn't define
99 Returns the string "no unload function" if the extension doesn't define
99 a function to unload itself, "not loaded" if the extension isn't loaded,
100 a function to unload itself, "not loaded" if the extension isn't loaded,
100 otherwise None.
101 otherwise None.
101 """
102 """
102 if module_str not in self.loaded:
103 if module_str not in self.loaded:
103 return "not loaded"
104 return "not loaded"
104
105
105 if module_str in sys.modules:
106 if module_str in sys.modules:
106 mod = sys.modules[module_str]
107 mod = sys.modules[module_str]
107 if self._call_unload_ipython_extension(mod):
108 if self._call_unload_ipython_extension(mod):
108 self.loaded.discard(module_str)
109 self.loaded.discard(module_str)
109 else:
110 else:
110 return "no unload function"
111 return "no unload function"
111
112
112 def reload_extension(self, module_str):
113 def reload_extension(self, module_str):
113 """Reload an IPython extension by calling reload.
114 """Reload an IPython extension by calling reload.
114
115
115 If the module has not been loaded before,
116 If the module has not been loaded before,
116 :meth:`InteractiveShell.load_extension` is called. Otherwise
117 :meth:`InteractiveShell.load_extension` is called. Otherwise
117 :func:`reload` is called and then the :func:`load_ipython_extension`
118 :func:`reload` is called and then the :func:`load_ipython_extension`
118 function of the module, if it exists is called.
119 function of the module, if it exists is called.
119 """
120 """
120 from IPython.utils.syspathcontext import prepended_to_syspath
121 from IPython.utils.syspathcontext import prepended_to_syspath
121
122
122 if (module_str in self.loaded) and (module_str in sys.modules):
123 if (module_str in self.loaded) and (module_str in sys.modules):
123 self.unload_extension(module_str)
124 self.unload_extension(module_str)
124 mod = sys.modules[module_str]
125 mod = sys.modules[module_str]
125 with prepended_to_syspath(self.ipython_extension_dir):
126 with prepended_to_syspath(self.ipython_extension_dir):
126 reload(mod)
127 reload(mod)
127 if self._call_load_ipython_extension(mod):
128 if self._call_load_ipython_extension(mod):
128 self.loaded.add(module_str)
129 self.loaded.add(module_str)
129 else:
130 else:
130 self.load_extension(module_str)
131 self.load_extension(module_str)
131
132
132 def _call_load_ipython_extension(self, mod):
133 def _call_load_ipython_extension(self, mod):
133 if hasattr(mod, 'load_ipython_extension'):
134 if hasattr(mod, 'load_ipython_extension'):
134 mod.load_ipython_extension(self.shell)
135 mod.load_ipython_extension(self.shell)
135 return True
136 return True
136
137
137 def _call_unload_ipython_extension(self, mod):
138 def _call_unload_ipython_extension(self, mod):
138 if hasattr(mod, 'unload_ipython_extension'):
139 if hasattr(mod, 'unload_ipython_extension'):
139 mod.unload_ipython_extension(self.shell)
140 mod.unload_ipython_extension(self.shell)
140 return True
141 return True
141
142
142 def install_extension(self, url, filename=None):
143 def install_extension(self, url, filename=None):
143 """Download and install an IPython extension.
144 """Download and install an IPython extension.
144
145
145 If filename is given, the file will be so named (inside the extension
146 If filename is given, the file will be so named (inside the extension
146 directory). Otherwise, the name from the URL will be used. The file must
147 directory). Otherwise, the name from the URL will be used. The file must
147 have a .py or .zip extension; otherwise, a ValueError will be raised.
148 have a .py or .zip extension; otherwise, a ValueError will be raised.
148
149
149 Returns the full path to the installed file.
150 Returns the full path to the installed file.
150 """
151 """
151 # Ensure the extension directory exists
152 # Ensure the extension directory exists
152 ensure_dir_exists(self.ipython_extension_dir)
153 ensure_dir_exists(self.ipython_extension_dir)
153
154
154 if os.path.isfile(url):
155 if os.path.isfile(url):
155 src_filename = os.path.basename(url)
156 src_filename = os.path.basename(url)
156 copy = copyfile
157 copy = copyfile
157 else:
158 else:
158 # Deferred imports
159 # Deferred imports
159 try:
160 try:
160 from urllib.parse import urlparse # Py3
161 from urllib.parse import urlparse # Py3
161 from urllib.request import urlretrieve
162 from urllib.request import urlretrieve
162 except ImportError:
163 except ImportError:
163 from urlparse import urlparse
164 from urlparse import urlparse
164 from urllib import urlretrieve
165 from urllib import urlretrieve
165 src_filename = urlparse(url).path.split('/')[-1]
166 src_filename = urlparse(url).path.split('/')[-1]
166 copy = urlretrieve
167 copy = urlretrieve
167
168
168 if filename is None:
169 if filename is None:
169 filename = src_filename
170 filename = src_filename
170 if os.path.splitext(filename)[1] not in ('.py', '.zip'):
171 if os.path.splitext(filename)[1] not in ('.py', '.zip'):
171 raise ValueError("The file must have a .py or .zip extension", filename)
172 raise ValueError("The file must have a .py or .zip extension", filename)
172
173
173 filename = os.path.join(self.ipython_extension_dir, filename)
174 filename = os.path.join(self.ipython_extension_dir, filename)
174 copy(url, filename)
175 copy(url, filename)
175 return filename
176 return filename
@@ -1,870 +1,872 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team.
3 # Copyright (C) 2010-2011 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the BSD License.
5 # Distributed under the terms of the BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import atexit
16 import atexit
17 import datetime
17 import datetime
18 import os
18 import os
19 import re
19 import re
20 try:
20 try:
21 import sqlite3
21 import sqlite3
22 except ImportError:
22 except ImportError:
23 try:
23 try:
24 from pysqlite2 import dbapi2 as sqlite3
24 from pysqlite2 import dbapi2 as sqlite3
25 except ImportError:
25 except ImportError:
26 sqlite3 = None
26 sqlite3 = None
27 import threading
27 import threading
28
28
29 # Our own packages
29 # Our own packages
30 from IPython.config.configurable import Configurable
30 from IPython.config.configurable import Configurable
31 from decorator import decorator
31 from decorator import decorator
32 from IPython.utils.decorators import undoc
32 from IPython.utils.decorators import undoc
33 from IPython.utils.path import locate_profile
33 from IPython.utils.path import locate_profile
34 from IPython.utils import py3compat
34 from IPython.utils import py3compat
35 from IPython.utils.traitlets import (
35 from IPython.utils.traitlets import (
36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
37 )
37 )
38 from IPython.utils.warn import warn
38 from IPython.utils.warn import warn
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Classes and functions
41 # Classes and functions
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 @undoc
44 @undoc
45 class DummyDB(object):
45 class DummyDB(object):
46 """Dummy DB that will act as a black hole for history.
46 """Dummy DB that will act as a black hole for history.
47
47
48 Only used in the absence of sqlite"""
48 Only used in the absence of sqlite"""
49 def execute(*args, **kwargs):
49 def execute(*args, **kwargs):
50 return []
50 return []
51
51
52 def commit(self, *args, **kwargs):
52 def commit(self, *args, **kwargs):
53 pass
53 pass
54
54
55 def __enter__(self, *args, **kwargs):
55 def __enter__(self, *args, **kwargs):
56 pass
56 pass
57
57
58 def __exit__(self, *args, **kwargs):
58 def __exit__(self, *args, **kwargs):
59 pass
59 pass
60
60
61
61
62 @decorator
62 @decorator
63 def needs_sqlite(f, self, *a, **kw):
63 def needs_sqlite(f, self, *a, **kw):
64 """Decorator: return an empty list in the absence of sqlite."""
64 """Decorator: return an empty list in the absence of sqlite."""
65 if sqlite3 is None or not self.enabled:
65 if sqlite3 is None or not self.enabled:
66 return []
66 return []
67 else:
67 else:
68 return f(self, *a, **kw)
68 return f(self, *a, **kw)
69
69
70
70
71 if sqlite3 is not None:
71 if sqlite3 is not None:
72 DatabaseError = sqlite3.DatabaseError
72 DatabaseError = sqlite3.DatabaseError
73 else:
73 else:
74 @undoc
74 @undoc
75 class DatabaseError(Exception):
75 class DatabaseError(Exception):
76 "Dummy exception when sqlite could not be imported. Should never occur."
76 "Dummy exception when sqlite could not be imported. Should never occur."
77
77
78 @decorator
78 @decorator
79 def catch_corrupt_db(f, self, *a, **kw):
79 def catch_corrupt_db(f, self, *a, **kw):
80 """A decorator which wraps HistoryAccessor method calls to catch errors from
80 """A decorator which wraps HistoryAccessor method calls to catch errors from
81 a corrupt SQLite database, move the old database out of the way, and create
81 a corrupt SQLite database, move the old database out of the way, and create
82 a new one.
82 a new one.
83 """
83 """
84 try:
84 try:
85 return f(self, *a, **kw)
85 return f(self, *a, **kw)
86 except DatabaseError:
86 except DatabaseError:
87 if os.path.isfile(self.hist_file):
87 if os.path.isfile(self.hist_file):
88 # Try to move the file out of the way
88 # Try to move the file out of the way
89 base,ext = os.path.splitext(self.hist_file)
89 base,ext = os.path.splitext(self.hist_file)
90 newpath = base + '-corrupt' + ext
90 newpath = base + '-corrupt' + ext
91 os.rename(self.hist_file, newpath)
91 os.rename(self.hist_file, newpath)
92 self.init_db()
92 self.init_db()
93 print("ERROR! History file wasn't a valid SQLite database.",
93 print("ERROR! History file wasn't a valid SQLite database.",
94 "It was moved to %s" % newpath, "and a new file created.")
94 "It was moved to %s" % newpath, "and a new file created.")
95 return []
95 return []
96
96
97 else:
97 else:
98 # The hist_file is probably :memory: or something else.
98 # The hist_file is probably :memory: or something else.
99 raise
99 raise
100
100
101 class HistoryAccessorBase(Configurable):
101 class HistoryAccessorBase(Configurable):
102 """An abstract class for History Accessors """
102 """An abstract class for History Accessors """
103
103
104 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
104 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
105 raise NotImplementedError
105 raise NotImplementedError
106
106
107 def search(self, pattern="*", raw=True, search_raw=True,
107 def search(self, pattern="*", raw=True, search_raw=True,
108 output=False, n=None, unique=False):
108 output=False, n=None, unique=False):
109 raise NotImplementedError
109 raise NotImplementedError
110
110
111 def get_range(self, session, start=1, stop=None, raw=True,output=False):
111 def get_range(self, session, start=1, stop=None, raw=True,output=False):
112 raise NotImplementedError
112 raise NotImplementedError
113
113
114 def get_range_by_str(self, rangestr, raw=True, output=False):
114 def get_range_by_str(self, rangestr, raw=True, output=False):
115 raise NotImplementedError
115 raise NotImplementedError
116
116
117
117
118 class HistoryAccessor(HistoryAccessorBase):
118 class HistoryAccessor(HistoryAccessorBase):
119 """Access the history database without adding to it.
119 """Access the history database without adding to it.
120
120
121 This is intended for use by standalone history tools. IPython shells use
121 This is intended for use by standalone history tools. IPython shells use
122 HistoryManager, below, which is a subclass of this."""
122 HistoryManager, below, which is a subclass of this."""
123
123
124 # String holding the path to the history file
124 # String holding the path to the history file
125 hist_file = Unicode(config=True,
125 hist_file = Unicode(config=True,
126 help="""Path to file to use for SQLite history database.
126 help="""Path to file to use for SQLite history database.
127
127
128 By default, IPython will put the history database in the IPython
128 By default, IPython will put the history database in the IPython
129 profile directory. If you would rather share one history among
129 profile directory. If you would rather share one history among
130 profiles, you can set this value in each, so that they are consistent.
130 profiles, you can set this value in each, so that they are consistent.
131
131
132 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
132 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
133 mounts. If you see IPython hanging, try setting this to something on a
133 mounts. If you see IPython hanging, try setting this to something on a
134 local disk, e.g::
134 local disk, e.g::
135
135
136 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
136 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
137
137
138 """)
138 """)
139
139
140 enabled = Bool(True, config=True,
140 enabled = Bool(True, config=True,
141 help="""enable the SQLite history
141 help="""enable the SQLite history
142
142
143 set enabled=False to disable the SQLite history,
143 set enabled=False to disable the SQLite history,
144 in which case there will be no stored history, no SQLite connection,
144 in which case there will be no stored history, no SQLite connection,
145 and no background saving thread. This may be necessary in some
145 and no background saving thread. This may be necessary in some
146 threaded environments where IPython is embedded.
146 threaded environments where IPython is embedded.
147 """
147 """
148 )
148 )
149
149
150 connection_options = Dict(config=True,
150 connection_options = Dict(config=True,
151 help="""Options for configuring the SQLite connection
151 help="""Options for configuring the SQLite connection
152
152
153 These options are passed as keyword args to sqlite3.connect
153 These options are passed as keyword args to sqlite3.connect
154 when establishing database conenctions.
154 when establishing database conenctions.
155 """
155 """
156 )
156 )
157
157
158 # The SQLite database
158 # The SQLite database
159 db = Any()
159 db = Any()
160 def _db_changed(self, name, old, new):
160 def _db_changed(self, name, old, new):
161 """validate the db, since it can be an Instance of two different types"""
161 """validate the db, since it can be an Instance of two different types"""
162 connection_types = (DummyDB,)
162 connection_types = (DummyDB,)
163 if sqlite3 is not None:
163 if sqlite3 is not None:
164 connection_types = (DummyDB, sqlite3.Connection)
164 connection_types = (DummyDB, sqlite3.Connection)
165 if not isinstance(new, connection_types):
165 if not isinstance(new, connection_types):
166 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
166 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
167 (self.__class__.__name__, new)
167 (self.__class__.__name__, new)
168 raise TraitError(msg)
168 raise TraitError(msg)
169
169
170 def __init__(self, profile='default', hist_file=u'', **traits):
170 def __init__(self, profile='default', hist_file=u'', **traits):
171 """Create a new history accessor.
171 """Create a new history accessor.
172
172
173 Parameters
173 Parameters
174 ----------
174 ----------
175 profile : str
175 profile : str
176 The name of the profile from which to open history.
176 The name of the profile from which to open history.
177 hist_file : str
177 hist_file : str
178 Path to an SQLite history database stored by IPython. If specified,
178 Path to an SQLite history database stored by IPython. If specified,
179 hist_file overrides profile.
179 hist_file overrides profile.
180 config : :class:`~IPython.config.loader.Config`
180 config : :class:`~IPython.config.loader.Config`
181 Config object. hist_file can also be set through this.
181 Config object. hist_file can also be set through this.
182 """
182 """
183 # We need a pointer back to the shell for various tasks.
183 # We need a pointer back to the shell for various tasks.
184 super(HistoryAccessor, self).__init__(**traits)
184 super(HistoryAccessor, self).__init__(**traits)
185 # defer setting hist_file from kwarg until after init,
185 # defer setting hist_file from kwarg until after init,
186 # otherwise the default kwarg value would clobber any value
186 # otherwise the default kwarg value would clobber any value
187 # set by config
187 # set by config
188 if hist_file:
188 if hist_file:
189 self.hist_file = hist_file
189 self.hist_file = hist_file
190
190
191 if self.hist_file == u'':
191 if self.hist_file == u'':
192 # No one has set the hist_file, yet.
192 # No one has set the hist_file, yet.
193 self.hist_file = self._get_hist_file_name(profile)
193 self.hist_file = self._get_hist_file_name(profile)
194
194
195 if sqlite3 is None and self.enabled:
195 if sqlite3 is None and self.enabled:
196 warn("IPython History requires SQLite, your history will not be saved")
196 warn("IPython History requires SQLite, your history will not be saved")
197 self.enabled = False
197 self.enabled = False
198
198
199 self.init_db()
199 self.init_db()
200
200
201 def _get_hist_file_name(self, profile='default'):
201 def _get_hist_file_name(self, profile='default'):
202 """Find the history file for the given profile name.
202 """Find the history file for the given profile name.
203
203
204 This is overridden by the HistoryManager subclass, to use the shell's
204 This is overridden by the HistoryManager subclass, to use the shell's
205 active profile.
205 active profile.
206
206
207 Parameters
207 Parameters
208 ----------
208 ----------
209 profile : str
209 profile : str
210 The name of a profile which has a history file.
210 The name of a profile which has a history file.
211 """
211 """
212 return os.path.join(locate_profile(profile), 'history.sqlite')
212 return os.path.join(locate_profile(profile), 'history.sqlite')
213
213
214 @catch_corrupt_db
214 @catch_corrupt_db
215 def init_db(self):
215 def init_db(self):
216 """Connect to the database, and create tables if necessary."""
216 """Connect to the database, and create tables if necessary."""
217 if not self.enabled:
217 if not self.enabled:
218 self.db = DummyDB()
218 self.db = DummyDB()
219 return
219 return
220
220
221 # use detect_types so that timestamps return datetime objects
221 # use detect_types so that timestamps return datetime objects
222 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
222 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
223 kwargs.update(self.connection_options)
223 kwargs.update(self.connection_options)
224 self.db = sqlite3.connect(self.hist_file, **kwargs)
224 self.db = sqlite3.connect(self.hist_file, **kwargs)
225 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
225 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
226 primary key autoincrement, start timestamp,
226 primary key autoincrement, start timestamp,
227 end timestamp, num_cmds integer, remark text)""")
227 end timestamp, num_cmds integer, remark text)""")
228 self.db.execute("""CREATE TABLE IF NOT EXISTS history
228 self.db.execute("""CREATE TABLE IF NOT EXISTS history
229 (session integer, line integer, source text, source_raw text,
229 (session integer, line integer, source text, source_raw text,
230 PRIMARY KEY (session, line))""")
230 PRIMARY KEY (session, line))""")
231 # Output history is optional, but ensure the table's there so it can be
231 # Output history is optional, but ensure the table's there so it can be
232 # enabled later.
232 # enabled later.
233 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
233 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
234 (session integer, line integer, output text,
234 (session integer, line integer, output text,
235 PRIMARY KEY (session, line))""")
235 PRIMARY KEY (session, line))""")
236 self.db.commit()
236 self.db.commit()
237
237
238 def writeout_cache(self):
238 def writeout_cache(self):
239 """Overridden by HistoryManager to dump the cache before certain
239 """Overridden by HistoryManager to dump the cache before certain
240 database lookups."""
240 database lookups."""
241 pass
241 pass
242
242
243 ## -------------------------------
243 ## -------------------------------
244 ## Methods for retrieving history:
244 ## Methods for retrieving history:
245 ## -------------------------------
245 ## -------------------------------
246 def _run_sql(self, sql, params, raw=True, output=False):
246 def _run_sql(self, sql, params, raw=True, output=False):
247 """Prepares and runs an SQL query for the history database.
247 """Prepares and runs an SQL query for the history database.
248
248
249 Parameters
249 Parameters
250 ----------
250 ----------
251 sql : str
251 sql : str
252 Any filtering expressions to go after SELECT ... FROM ...
252 Any filtering expressions to go after SELECT ... FROM ...
253 params : tuple
253 params : tuple
254 Parameters passed to the SQL query (to replace "?")
254 Parameters passed to the SQL query (to replace "?")
255 raw, output : bool
255 raw, output : bool
256 See :meth:`get_range`
256 See :meth:`get_range`
257
257
258 Returns
258 Returns
259 -------
259 -------
260 Tuples as :meth:`get_range`
260 Tuples as :meth:`get_range`
261 """
261 """
262 toget = 'source_raw' if raw else 'source'
262 toget = 'source_raw' if raw else 'source'
263 sqlfrom = "history"
263 sqlfrom = "history"
264 if output:
264 if output:
265 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
265 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
266 toget = "history.%s, output_history.output" % toget
266 toget = "history.%s, output_history.output" % toget
267 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
267 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
268 (toget, sqlfrom) + sql, params)
268 (toget, sqlfrom) + sql, params)
269 if output: # Regroup into 3-tuples, and parse JSON
269 if output: # Regroup into 3-tuples, and parse JSON
270 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
270 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
271 return cur
271 return cur
272
272
273 @needs_sqlite
273 @needs_sqlite
274 @catch_corrupt_db
274 @catch_corrupt_db
275 def get_session_info(self, session):
275 def get_session_info(self, session):
276 """Get info about a session.
276 """Get info about a session.
277
277
278 Parameters
278 Parameters
279 ----------
279 ----------
280
280
281 session : int
281 session : int
282 Session number to retrieve.
282 Session number to retrieve.
283
283
284 Returns
284 Returns
285 -------
285 -------
286
286
287 session_id : int
287 session_id : int
288 Session ID number
288 Session ID number
289 start : datetime
289 start : datetime
290 Timestamp for the start of the session.
290 Timestamp for the start of the session.
291 end : datetime
291 end : datetime
292 Timestamp for the end of the session, or None if IPython crashed.
292 Timestamp for the end of the session, or None if IPython crashed.
293 num_cmds : int
293 num_cmds : int
294 Number of commands run, or None if IPython crashed.
294 Number of commands run, or None if IPython crashed.
295 remark : unicode
295 remark : unicode
296 A manually set description.
296 A manually set description.
297 """
297 """
298 query = "SELECT * from sessions where session == ?"
298 query = "SELECT * from sessions where session == ?"
299 return self.db.execute(query, (session,)).fetchone()
299 return self.db.execute(query, (session,)).fetchone()
300
300
301 @catch_corrupt_db
301 @catch_corrupt_db
302 def get_last_session_id(self):
302 def get_last_session_id(self):
303 """Get the last session ID currently in the database.
303 """Get the last session ID currently in the database.
304
304
305 Within IPython, this should be the same as the value stored in
305 Within IPython, this should be the same as the value stored in
306 :attr:`HistoryManager.session_number`.
306 :attr:`HistoryManager.session_number`.
307 """
307 """
308 for record in self.get_tail(n=1, include_latest=True):
308 for record in self.get_tail(n=1, include_latest=True):
309 return record[0]
309 return record[0]
310
310
311 @catch_corrupt_db
311 @catch_corrupt_db
312 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
312 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
313 """Get the last n lines from the history database.
313 """Get the last n lines from the history database.
314
314
315 Parameters
315 Parameters
316 ----------
316 ----------
317 n : int
317 n : int
318 The number of lines to get
318 The number of lines to get
319 raw, output : bool
319 raw, output : bool
320 See :meth:`get_range`
320 See :meth:`get_range`
321 include_latest : bool
321 include_latest : bool
322 If False (default), n+1 lines are fetched, and the latest one
322 If False (default), n+1 lines are fetched, and the latest one
323 is discarded. This is intended to be used where the function
323 is discarded. This is intended to be used where the function
324 is called by a user command, which it should not return.
324 is called by a user command, which it should not return.
325
325
326 Returns
326 Returns
327 -------
327 -------
328 Tuples as :meth:`get_range`
328 Tuples as :meth:`get_range`
329 """
329 """
330 self.writeout_cache()
330 self.writeout_cache()
331 if not include_latest:
331 if not include_latest:
332 n += 1
332 n += 1
333 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
333 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
334 (n,), raw=raw, output=output)
334 (n,), raw=raw, output=output)
335 if not include_latest:
335 if not include_latest:
336 return reversed(list(cur)[1:])
336 return reversed(list(cur)[1:])
337 return reversed(list(cur))
337 return reversed(list(cur))
338
338
339 @catch_corrupt_db
339 @catch_corrupt_db
340 def search(self, pattern="*", raw=True, search_raw=True,
340 def search(self, pattern="*", raw=True, search_raw=True,
341 output=False, n=None, unique=False):
341 output=False, n=None, unique=False):
342 """Search the database using unix glob-style matching (wildcards
342 """Search the database using unix glob-style matching (wildcards
343 * and ?).
343 * and ?).
344
344
345 Parameters
345 Parameters
346 ----------
346 ----------
347 pattern : str
347 pattern : str
348 The wildcarded pattern to match when searching
348 The wildcarded pattern to match when searching
349 search_raw : bool
349 search_raw : bool
350 If True, search the raw input, otherwise, the parsed input
350 If True, search the raw input, otherwise, the parsed input
351 raw, output : bool
351 raw, output : bool
352 See :meth:`get_range`
352 See :meth:`get_range`
353 n : None or int
353 n : None or int
354 If an integer is given, it defines the limit of
354 If an integer is given, it defines the limit of
355 returned entries.
355 returned entries.
356 unique : bool
356 unique : bool
357 When it is true, return only unique entries.
357 When it is true, return only unique entries.
358
358
359 Returns
359 Returns
360 -------
360 -------
361 Tuples as :meth:`get_range`
361 Tuples as :meth:`get_range`
362 """
362 """
363 tosearch = "source_raw" if search_raw else "source"
363 tosearch = "source_raw" if search_raw else "source"
364 if output:
364 if output:
365 tosearch = "history." + tosearch
365 tosearch = "history." + tosearch
366 self.writeout_cache()
366 self.writeout_cache()
367 sqlform = "WHERE %s GLOB ?" % tosearch
367 sqlform = "WHERE %s GLOB ?" % tosearch
368 params = (pattern,)
368 params = (pattern,)
369 if unique:
369 if unique:
370 sqlform += ' GROUP BY {0}'.format(tosearch)
370 sqlform += ' GROUP BY {0}'.format(tosearch)
371 if n is not None:
371 if n is not None:
372 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
372 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
373 params += (n,)
373 params += (n,)
374 elif unique:
374 elif unique:
375 sqlform += " ORDER BY session, line"
375 sqlform += " ORDER BY session, line"
376 cur = self._run_sql(sqlform, params, raw=raw, output=output)
376 cur = self._run_sql(sqlform, params, raw=raw, output=output)
377 if n is not None:
377 if n is not None:
378 return reversed(list(cur))
378 return reversed(list(cur))
379 return cur
379 return cur
380
380
381 @catch_corrupt_db
381 @catch_corrupt_db
382 def get_range(self, session, start=1, stop=None, raw=True,output=False):
382 def get_range(self, session, start=1, stop=None, raw=True,output=False):
383 """Retrieve input by session.
383 """Retrieve input by session.
384
384
385 Parameters
385 Parameters
386 ----------
386 ----------
387 session : int
387 session : int
388 Session number to retrieve.
388 Session number to retrieve.
389 start : int
389 start : int
390 First line to retrieve.
390 First line to retrieve.
391 stop : int
391 stop : int
392 End of line range (excluded from output itself). If None, retrieve
392 End of line range (excluded from output itself). If None, retrieve
393 to the end of the session.
393 to the end of the session.
394 raw : bool
394 raw : bool
395 If True, return untranslated input
395 If True, return untranslated input
396 output : bool
396 output : bool
397 If True, attempt to include output. This will be 'real' Python
397 If True, attempt to include output. This will be 'real' Python
398 objects for the current session, or text reprs from previous
398 objects for the current session, or text reprs from previous
399 sessions if db_log_output was enabled at the time. Where no output
399 sessions if db_log_output was enabled at the time. Where no output
400 is found, None is used.
400 is found, None is used.
401
401
402 Returns
402 Returns
403 -------
403 -------
404 entries
404 entries
405 An iterator over the desired lines. Each line is a 3-tuple, either
405 An iterator over the desired lines. Each line is a 3-tuple, either
406 (session, line, input) if output is False, or
406 (session, line, input) if output is False, or
407 (session, line, (input, output)) if output is True.
407 (session, line, (input, output)) if output is True.
408 """
408 """
409 if stop:
409 if stop:
410 lineclause = "line >= ? AND line < ?"
410 lineclause = "line >= ? AND line < ?"
411 params = (session, start, stop)
411 params = (session, start, stop)
412 else:
412 else:
413 lineclause = "line>=?"
413 lineclause = "line>=?"
414 params = (session, start)
414 params = (session, start)
415
415
416 return self._run_sql("WHERE session==? AND %s" % lineclause,
416 return self._run_sql("WHERE session==? AND %s" % lineclause,
417 params, raw=raw, output=output)
417 params, raw=raw, output=output)
418
418
419 def get_range_by_str(self, rangestr, raw=True, output=False):
419 def get_range_by_str(self, rangestr, raw=True, output=False):
420 """Get lines of history from a string of ranges, as used by magic
420 """Get lines of history from a string of ranges, as used by magic
421 commands %hist, %save, %macro, etc.
421 commands %hist, %save, %macro, etc.
422
422
423 Parameters
423 Parameters
424 ----------
424 ----------
425 rangestr : str
425 rangestr : str
426 A string specifying ranges, e.g. "5 ~2/1-4". See
426 A string specifying ranges, e.g. "5 ~2/1-4". See
427 :func:`magic_history` for full details.
427 :func:`magic_history` for full details.
428 raw, output : bool
428 raw, output : bool
429 As :meth:`get_range`
429 As :meth:`get_range`
430
430
431 Returns
431 Returns
432 -------
432 -------
433 Tuples as :meth:`get_range`
433 Tuples as :meth:`get_range`
434 """
434 """
435 for sess, s, e in extract_hist_ranges(rangestr):
435 for sess, s, e in extract_hist_ranges(rangestr):
436 for line in self.get_range(sess, s, e, raw=raw, output=output):
436 for line in self.get_range(sess, s, e, raw=raw, output=output):
437 yield line
437 yield line
438
438
439
439
440 class HistoryManager(HistoryAccessor):
440 class HistoryManager(HistoryAccessor):
441 """A class to organize all history-related functionality in one place.
441 """A class to organize all history-related functionality in one place.
442 """
442 """
443 # Public interface
443 # Public interface
444
444
445 # An instance of the IPython shell we are attached to
445 # An instance of the IPython shell we are attached to
446 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
446 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
447 allow_none=True)
447 # Lists to hold processed and raw history. These start with a blank entry
448 # Lists to hold processed and raw history. These start with a blank entry
448 # so that we can index them starting from 1
449 # so that we can index them starting from 1
449 input_hist_parsed = List([""])
450 input_hist_parsed = List([""])
450 input_hist_raw = List([""])
451 input_hist_raw = List([""])
451 # A list of directories visited during session
452 # A list of directories visited during session
452 dir_hist = List()
453 dir_hist = List()
453 def _dir_hist_default(self):
454 def _dir_hist_default(self):
454 try:
455 try:
455 return [py3compat.getcwd()]
456 return [py3compat.getcwd()]
456 except OSError:
457 except OSError:
457 return []
458 return []
458
459
459 # A dict of output history, keyed with ints from the shell's
460 # A dict of output history, keyed with ints from the shell's
460 # execution count.
461 # execution count.
461 output_hist = Dict()
462 output_hist = Dict()
462 # The text/plain repr of outputs.
463 # The text/plain repr of outputs.
463 output_hist_reprs = Dict()
464 output_hist_reprs = Dict()
464
465
465 # The number of the current session in the history database
466 # The number of the current session in the history database
466 session_number = Integer()
467 session_number = Integer()
467
468
468 db_log_output = Bool(False, config=True,
469 db_log_output = Bool(False, config=True,
469 help="Should the history database include output? (default: no)"
470 help="Should the history database include output? (default: no)"
470 )
471 )
471 db_cache_size = Integer(0, config=True,
472 db_cache_size = Integer(0, config=True,
472 help="Write to database every x commands (higher values save disk access & power).\n"
473 help="Write to database every x commands (higher values save disk access & power).\n"
473 "Values of 1 or less effectively disable caching."
474 "Values of 1 or less effectively disable caching."
474 )
475 )
475 # The input and output caches
476 # The input and output caches
476 db_input_cache = List()
477 db_input_cache = List()
477 db_output_cache = List()
478 db_output_cache = List()
478
479
479 # History saving in separate thread
480 # History saving in separate thread
480 save_thread = Instance('IPython.core.history.HistorySavingThread')
481 save_thread = Instance('IPython.core.history.HistorySavingThread',
482 allow_none=True)
481 try: # Event is a function returning an instance of _Event...
483 try: # Event is a function returning an instance of _Event...
482 save_flag = Instance(threading._Event)
484 save_flag = Instance(threading._Event, allow_none=True)
483 except AttributeError: # ...until Python 3.3, when it's a class.
485 except AttributeError: # ...until Python 3.3, when it's a class.
484 save_flag = Instance(threading.Event)
486 save_flag = Instance(threading.Event, allow_none=True)
485
487
486 # Private interface
488 # Private interface
487 # Variables used to store the three last inputs from the user. On each new
489 # Variables used to store the three last inputs from the user. On each new
488 # history update, we populate the user's namespace with these, shifted as
490 # history update, we populate the user's namespace with these, shifted as
489 # necessary.
491 # necessary.
490 _i00 = Unicode(u'')
492 _i00 = Unicode(u'')
491 _i = Unicode(u'')
493 _i = Unicode(u'')
492 _ii = Unicode(u'')
494 _ii = Unicode(u'')
493 _iii = Unicode(u'')
495 _iii = Unicode(u'')
494
496
495 # A regex matching all forms of the exit command, so that we don't store
497 # A regex matching all forms of the exit command, so that we don't store
496 # them in the history (it's annoying to rewind the first entry and land on
498 # them in the history (it's annoying to rewind the first entry and land on
497 # an exit call).
499 # an exit call).
498 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
500 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
499
501
500 def __init__(self, shell=None, config=None, **traits):
502 def __init__(self, shell=None, config=None, **traits):
501 """Create a new history manager associated with a shell instance.
503 """Create a new history manager associated with a shell instance.
502 """
504 """
503 # We need a pointer back to the shell for various tasks.
505 # We need a pointer back to the shell for various tasks.
504 super(HistoryManager, self).__init__(shell=shell, config=config,
506 super(HistoryManager, self).__init__(shell=shell, config=config,
505 **traits)
507 **traits)
506 self.save_flag = threading.Event()
508 self.save_flag = threading.Event()
507 self.db_input_cache_lock = threading.Lock()
509 self.db_input_cache_lock = threading.Lock()
508 self.db_output_cache_lock = threading.Lock()
510 self.db_output_cache_lock = threading.Lock()
509 if self.enabled and self.hist_file != ':memory:':
511 if self.enabled and self.hist_file != ':memory:':
510 self.save_thread = HistorySavingThread(self)
512 self.save_thread = HistorySavingThread(self)
511 self.save_thread.start()
513 self.save_thread.start()
512
514
513 self.new_session()
515 self.new_session()
514
516
515 def _get_hist_file_name(self, profile=None):
517 def _get_hist_file_name(self, profile=None):
516 """Get default history file name based on the Shell's profile.
518 """Get default history file name based on the Shell's profile.
517
519
518 The profile parameter is ignored, but must exist for compatibility with
520 The profile parameter is ignored, but must exist for compatibility with
519 the parent class."""
521 the parent class."""
520 profile_dir = self.shell.profile_dir.location
522 profile_dir = self.shell.profile_dir.location
521 return os.path.join(profile_dir, 'history.sqlite')
523 return os.path.join(profile_dir, 'history.sqlite')
522
524
523 @needs_sqlite
525 @needs_sqlite
524 def new_session(self, conn=None):
526 def new_session(self, conn=None):
525 """Get a new session number."""
527 """Get a new session number."""
526 if conn is None:
528 if conn is None:
527 conn = self.db
529 conn = self.db
528
530
529 with conn:
531 with conn:
530 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
532 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
531 NULL, "") """, (datetime.datetime.now(),))
533 NULL, "") """, (datetime.datetime.now(),))
532 self.session_number = cur.lastrowid
534 self.session_number = cur.lastrowid
533
535
534 def end_session(self):
536 def end_session(self):
535 """Close the database session, filling in the end time and line count."""
537 """Close the database session, filling in the end time and line count."""
536 self.writeout_cache()
538 self.writeout_cache()
537 with self.db:
539 with self.db:
538 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
540 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
539 session==?""", (datetime.datetime.now(),
541 session==?""", (datetime.datetime.now(),
540 len(self.input_hist_parsed)-1, self.session_number))
542 len(self.input_hist_parsed)-1, self.session_number))
541 self.session_number = 0
543 self.session_number = 0
542
544
543 def name_session(self, name):
545 def name_session(self, name):
544 """Give the current session a name in the history database."""
546 """Give the current session a name in the history database."""
545 with self.db:
547 with self.db:
546 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
548 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
547 (name, self.session_number))
549 (name, self.session_number))
548
550
549 def reset(self, new_session=True):
551 def reset(self, new_session=True):
550 """Clear the session history, releasing all object references, and
552 """Clear the session history, releasing all object references, and
551 optionally open a new session."""
553 optionally open a new session."""
552 self.output_hist.clear()
554 self.output_hist.clear()
553 # The directory history can't be completely empty
555 # The directory history can't be completely empty
554 self.dir_hist[:] = [py3compat.getcwd()]
556 self.dir_hist[:] = [py3compat.getcwd()]
555
557
556 if new_session:
558 if new_session:
557 if self.session_number:
559 if self.session_number:
558 self.end_session()
560 self.end_session()
559 self.input_hist_parsed[:] = [""]
561 self.input_hist_parsed[:] = [""]
560 self.input_hist_raw[:] = [""]
562 self.input_hist_raw[:] = [""]
561 self.new_session()
563 self.new_session()
562
564
563 # ------------------------------
565 # ------------------------------
564 # Methods for retrieving history
566 # Methods for retrieving history
565 # ------------------------------
567 # ------------------------------
566 def get_session_info(self, session=0):
568 def get_session_info(self, session=0):
567 """Get info about a session.
569 """Get info about a session.
568
570
569 Parameters
571 Parameters
570 ----------
572 ----------
571
573
572 session : int
574 session : int
573 Session number to retrieve. The current session is 0, and negative
575 Session number to retrieve. The current session is 0, and negative
574 numbers count back from current session, so -1 is the previous session.
576 numbers count back from current session, so -1 is the previous session.
575
577
576 Returns
578 Returns
577 -------
579 -------
578
580
579 session_id : int
581 session_id : int
580 Session ID number
582 Session ID number
581 start : datetime
583 start : datetime
582 Timestamp for the start of the session.
584 Timestamp for the start of the session.
583 end : datetime
585 end : datetime
584 Timestamp for the end of the session, or None if IPython crashed.
586 Timestamp for the end of the session, or None if IPython crashed.
585 num_cmds : int
587 num_cmds : int
586 Number of commands run, or None if IPython crashed.
588 Number of commands run, or None if IPython crashed.
587 remark : unicode
589 remark : unicode
588 A manually set description.
590 A manually set description.
589 """
591 """
590 if session <= 0:
592 if session <= 0:
591 session += self.session_number
593 session += self.session_number
592
594
593 return super(HistoryManager, self).get_session_info(session=session)
595 return super(HistoryManager, self).get_session_info(session=session)
594
596
595 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
597 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
596 """Get input and output history from the current session. Called by
598 """Get input and output history from the current session. Called by
597 get_range, and takes similar parameters."""
599 get_range, and takes similar parameters."""
598 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
600 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
599
601
600 n = len(input_hist)
602 n = len(input_hist)
601 if start < 0:
603 if start < 0:
602 start += n
604 start += n
603 if not stop or (stop > n):
605 if not stop or (stop > n):
604 stop = n
606 stop = n
605 elif stop < 0:
607 elif stop < 0:
606 stop += n
608 stop += n
607
609
608 for i in range(start, stop):
610 for i in range(start, stop):
609 if output:
611 if output:
610 line = (input_hist[i], self.output_hist_reprs.get(i))
612 line = (input_hist[i], self.output_hist_reprs.get(i))
611 else:
613 else:
612 line = input_hist[i]
614 line = input_hist[i]
613 yield (0, i, line)
615 yield (0, i, line)
614
616
615 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
617 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
616 """Retrieve input by session.
618 """Retrieve input by session.
617
619
618 Parameters
620 Parameters
619 ----------
621 ----------
620 session : int
622 session : int
621 Session number to retrieve. The current session is 0, and negative
623 Session number to retrieve. The current session is 0, and negative
622 numbers count back from current session, so -1 is previous session.
624 numbers count back from current session, so -1 is previous session.
623 start : int
625 start : int
624 First line to retrieve.
626 First line to retrieve.
625 stop : int
627 stop : int
626 End of line range (excluded from output itself). If None, retrieve
628 End of line range (excluded from output itself). If None, retrieve
627 to the end of the session.
629 to the end of the session.
628 raw : bool
630 raw : bool
629 If True, return untranslated input
631 If True, return untranslated input
630 output : bool
632 output : bool
631 If True, attempt to include output. This will be 'real' Python
633 If True, attempt to include output. This will be 'real' Python
632 objects for the current session, or text reprs from previous
634 objects for the current session, or text reprs from previous
633 sessions if db_log_output was enabled at the time. Where no output
635 sessions if db_log_output was enabled at the time. Where no output
634 is found, None is used.
636 is found, None is used.
635
637
636 Returns
638 Returns
637 -------
639 -------
638 entries
640 entries
639 An iterator over the desired lines. Each line is a 3-tuple, either
641 An iterator over the desired lines. Each line is a 3-tuple, either
640 (session, line, input) if output is False, or
642 (session, line, input) if output is False, or
641 (session, line, (input, output)) if output is True.
643 (session, line, (input, output)) if output is True.
642 """
644 """
643 if session <= 0:
645 if session <= 0:
644 session += self.session_number
646 session += self.session_number
645 if session==self.session_number: # Current session
647 if session==self.session_number: # Current session
646 return self._get_range_session(start, stop, raw, output)
648 return self._get_range_session(start, stop, raw, output)
647 return super(HistoryManager, self).get_range(session, start, stop, raw,
649 return super(HistoryManager, self).get_range(session, start, stop, raw,
648 output)
650 output)
649
651
650 ## ----------------------------
652 ## ----------------------------
651 ## Methods for storing history:
653 ## Methods for storing history:
652 ## ----------------------------
654 ## ----------------------------
653 def store_inputs(self, line_num, source, source_raw=None):
655 def store_inputs(self, line_num, source, source_raw=None):
654 """Store source and raw input in history and create input cache
656 """Store source and raw input in history and create input cache
655 variables ``_i*``.
657 variables ``_i*``.
656
658
657 Parameters
659 Parameters
658 ----------
660 ----------
659 line_num : int
661 line_num : int
660 The prompt number of this input.
662 The prompt number of this input.
661
663
662 source : str
664 source : str
663 Python input.
665 Python input.
664
666
665 source_raw : str, optional
667 source_raw : str, optional
666 If given, this is the raw input without any IPython transformations
668 If given, this is the raw input without any IPython transformations
667 applied to it. If not given, ``source`` is used.
669 applied to it. If not given, ``source`` is used.
668 """
670 """
669 if source_raw is None:
671 if source_raw is None:
670 source_raw = source
672 source_raw = source
671 source = source.rstrip('\n')
673 source = source.rstrip('\n')
672 source_raw = source_raw.rstrip('\n')
674 source_raw = source_raw.rstrip('\n')
673
675
674 # do not store exit/quit commands
676 # do not store exit/quit commands
675 if self._exit_re.match(source_raw.strip()):
677 if self._exit_re.match(source_raw.strip()):
676 return
678 return
677
679
678 self.input_hist_parsed.append(source)
680 self.input_hist_parsed.append(source)
679 self.input_hist_raw.append(source_raw)
681 self.input_hist_raw.append(source_raw)
680
682
681 with self.db_input_cache_lock:
683 with self.db_input_cache_lock:
682 self.db_input_cache.append((line_num, source, source_raw))
684 self.db_input_cache.append((line_num, source, source_raw))
683 # Trigger to flush cache and write to DB.
685 # Trigger to flush cache and write to DB.
684 if len(self.db_input_cache) >= self.db_cache_size:
686 if len(self.db_input_cache) >= self.db_cache_size:
685 self.save_flag.set()
687 self.save_flag.set()
686
688
687 # update the auto _i variables
689 # update the auto _i variables
688 self._iii = self._ii
690 self._iii = self._ii
689 self._ii = self._i
691 self._ii = self._i
690 self._i = self._i00
692 self._i = self._i00
691 self._i00 = source_raw
693 self._i00 = source_raw
692
694
693 # hackish access to user namespace to create _i1,_i2... dynamically
695 # hackish access to user namespace to create _i1,_i2... dynamically
694 new_i = '_i%s' % line_num
696 new_i = '_i%s' % line_num
695 to_main = {'_i': self._i,
697 to_main = {'_i': self._i,
696 '_ii': self._ii,
698 '_ii': self._ii,
697 '_iii': self._iii,
699 '_iii': self._iii,
698 new_i : self._i00 }
700 new_i : self._i00 }
699
701
700 if self.shell is not None:
702 if self.shell is not None:
701 self.shell.push(to_main, interactive=False)
703 self.shell.push(to_main, interactive=False)
702
704
703 def store_output(self, line_num):
705 def store_output(self, line_num):
704 """If database output logging is enabled, this saves all the
706 """If database output logging is enabled, this saves all the
705 outputs from the indicated prompt number to the database. It's
707 outputs from the indicated prompt number to the database. It's
706 called by run_cell after code has been executed.
708 called by run_cell after code has been executed.
707
709
708 Parameters
710 Parameters
709 ----------
711 ----------
710 line_num : int
712 line_num : int
711 The line number from which to save outputs
713 The line number from which to save outputs
712 """
714 """
713 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
715 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
714 return
716 return
715 output = self.output_hist_reprs[line_num]
717 output = self.output_hist_reprs[line_num]
716
718
717 with self.db_output_cache_lock:
719 with self.db_output_cache_lock:
718 self.db_output_cache.append((line_num, output))
720 self.db_output_cache.append((line_num, output))
719 if self.db_cache_size <= 1:
721 if self.db_cache_size <= 1:
720 self.save_flag.set()
722 self.save_flag.set()
721
723
722 def _writeout_input_cache(self, conn):
724 def _writeout_input_cache(self, conn):
723 with conn:
725 with conn:
724 for line in self.db_input_cache:
726 for line in self.db_input_cache:
725 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
727 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
726 (self.session_number,)+line)
728 (self.session_number,)+line)
727
729
728 def _writeout_output_cache(self, conn):
730 def _writeout_output_cache(self, conn):
729 with conn:
731 with conn:
730 for line in self.db_output_cache:
732 for line in self.db_output_cache:
731 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
733 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
732 (self.session_number,)+line)
734 (self.session_number,)+line)
733
735
734 @needs_sqlite
736 @needs_sqlite
735 def writeout_cache(self, conn=None):
737 def writeout_cache(self, conn=None):
736 """Write any entries in the cache to the database."""
738 """Write any entries in the cache to the database."""
737 if conn is None:
739 if conn is None:
738 conn = self.db
740 conn = self.db
739
741
740 with self.db_input_cache_lock:
742 with self.db_input_cache_lock:
741 try:
743 try:
742 self._writeout_input_cache(conn)
744 self._writeout_input_cache(conn)
743 except sqlite3.IntegrityError:
745 except sqlite3.IntegrityError:
744 self.new_session(conn)
746 self.new_session(conn)
745 print("ERROR! Session/line number was not unique in",
747 print("ERROR! Session/line number was not unique in",
746 "database. History logging moved to new session",
748 "database. History logging moved to new session",
747 self.session_number)
749 self.session_number)
748 try:
750 try:
749 # Try writing to the new session. If this fails, don't
751 # Try writing to the new session. If this fails, don't
750 # recurse
752 # recurse
751 self._writeout_input_cache(conn)
753 self._writeout_input_cache(conn)
752 except sqlite3.IntegrityError:
754 except sqlite3.IntegrityError:
753 pass
755 pass
754 finally:
756 finally:
755 self.db_input_cache = []
757 self.db_input_cache = []
756
758
757 with self.db_output_cache_lock:
759 with self.db_output_cache_lock:
758 try:
760 try:
759 self._writeout_output_cache(conn)
761 self._writeout_output_cache(conn)
760 except sqlite3.IntegrityError:
762 except sqlite3.IntegrityError:
761 print("!! Session/line number for output was not unique",
763 print("!! Session/line number for output was not unique",
762 "in database. Output will not be stored.")
764 "in database. Output will not be stored.")
763 finally:
765 finally:
764 self.db_output_cache = []
766 self.db_output_cache = []
765
767
766
768
767 class HistorySavingThread(threading.Thread):
769 class HistorySavingThread(threading.Thread):
768 """This thread takes care of writing history to the database, so that
770 """This thread takes care of writing history to the database, so that
769 the UI isn't held up while that happens.
771 the UI isn't held up while that happens.
770
772
771 It waits for the HistoryManager's save_flag to be set, then writes out
773 It waits for the HistoryManager's save_flag to be set, then writes out
772 the history cache. The main thread is responsible for setting the flag when
774 the history cache. The main thread is responsible for setting the flag when
773 the cache size reaches a defined threshold."""
775 the cache size reaches a defined threshold."""
774 daemon = True
776 daemon = True
775 stop_now = False
777 stop_now = False
776 enabled = True
778 enabled = True
777 def __init__(self, history_manager):
779 def __init__(self, history_manager):
778 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
780 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
779 self.history_manager = history_manager
781 self.history_manager = history_manager
780 self.enabled = history_manager.enabled
782 self.enabled = history_manager.enabled
781 atexit.register(self.stop)
783 atexit.register(self.stop)
782
784
783 @needs_sqlite
785 @needs_sqlite
784 def run(self):
786 def run(self):
785 # We need a separate db connection per thread:
787 # We need a separate db connection per thread:
786 try:
788 try:
787 self.db = sqlite3.connect(self.history_manager.hist_file,
789 self.db = sqlite3.connect(self.history_manager.hist_file,
788 **self.history_manager.connection_options
790 **self.history_manager.connection_options
789 )
791 )
790 while True:
792 while True:
791 self.history_manager.save_flag.wait()
793 self.history_manager.save_flag.wait()
792 if self.stop_now:
794 if self.stop_now:
793 self.db.close()
795 self.db.close()
794 return
796 return
795 self.history_manager.save_flag.clear()
797 self.history_manager.save_flag.clear()
796 self.history_manager.writeout_cache(self.db)
798 self.history_manager.writeout_cache(self.db)
797 except Exception as e:
799 except Exception as e:
798 print(("The history saving thread hit an unexpected error (%s)."
800 print(("The history saving thread hit an unexpected error (%s)."
799 "History will not be written to the database.") % repr(e))
801 "History will not be written to the database.") % repr(e))
800
802
801 def stop(self):
803 def stop(self):
802 """This can be called from the main thread to safely stop this thread.
804 """This can be called from the main thread to safely stop this thread.
803
805
804 Note that it does not attempt to write out remaining history before
806 Note that it does not attempt to write out remaining history before
805 exiting. That should be done by calling the HistoryManager's
807 exiting. That should be done by calling the HistoryManager's
806 end_session method."""
808 end_session method."""
807 self.stop_now = True
809 self.stop_now = True
808 self.history_manager.save_flag.set()
810 self.history_manager.save_flag.set()
809 self.join()
811 self.join()
810
812
811
813
812 # To match, e.g. ~5/8-~2/3
814 # To match, e.g. ~5/8-~2/3
813 range_re = re.compile(r"""
815 range_re = re.compile(r"""
814 ((?P<startsess>~?\d+)/)?
816 ((?P<startsess>~?\d+)/)?
815 (?P<start>\d+)?
817 (?P<start>\d+)?
816 ((?P<sep>[\-:])
818 ((?P<sep>[\-:])
817 ((?P<endsess>~?\d+)/)?
819 ((?P<endsess>~?\d+)/)?
818 (?P<end>\d+))?
820 (?P<end>\d+))?
819 $""", re.VERBOSE)
821 $""", re.VERBOSE)
820
822
821
823
822 def extract_hist_ranges(ranges_str):
824 def extract_hist_ranges(ranges_str):
823 """Turn a string of history ranges into 3-tuples of (session, start, stop).
825 """Turn a string of history ranges into 3-tuples of (session, start, stop).
824
826
825 Examples
827 Examples
826 --------
828 --------
827 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
829 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
828 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
830 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
829 """
831 """
830 for range_str in ranges_str.split():
832 for range_str in ranges_str.split():
831 rmatch = range_re.match(range_str)
833 rmatch = range_re.match(range_str)
832 if not rmatch:
834 if not rmatch:
833 continue
835 continue
834 start = rmatch.group("start")
836 start = rmatch.group("start")
835 if start:
837 if start:
836 start = int(start)
838 start = int(start)
837 end = rmatch.group("end")
839 end = rmatch.group("end")
838 # If no end specified, get (a, a + 1)
840 # If no end specified, get (a, a + 1)
839 end = int(end) if end else start + 1
841 end = int(end) if end else start + 1
840 else: # start not specified
842 else: # start not specified
841 if not rmatch.group('startsess'): # no startsess
843 if not rmatch.group('startsess'): # no startsess
842 continue
844 continue
843 start = 1
845 start = 1
844 end = None # provide the entire session hist
846 end = None # provide the entire session hist
845
847
846 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
848 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
847 end += 1
849 end += 1
848 startsess = rmatch.group("startsess") or "0"
850 startsess = rmatch.group("startsess") or "0"
849 endsess = rmatch.group("endsess") or startsess
851 endsess = rmatch.group("endsess") or startsess
850 startsess = int(startsess.replace("~","-"))
852 startsess = int(startsess.replace("~","-"))
851 endsess = int(endsess.replace("~","-"))
853 endsess = int(endsess.replace("~","-"))
852 assert endsess >= startsess, "start session must be earlier than end session"
854 assert endsess >= startsess, "start session must be earlier than end session"
853
855
854 if endsess == startsess:
856 if endsess == startsess:
855 yield (startsess, start, end)
857 yield (startsess, start, end)
856 continue
858 continue
857 # Multiple sessions in one range:
859 # Multiple sessions in one range:
858 yield (startsess, start, None)
860 yield (startsess, start, None)
859 for sess in range(startsess+1, endsess):
861 for sess in range(startsess+1, endsess):
860 yield (sess, 1, None)
862 yield (sess, 1, None)
861 yield (endsess, 1, end)
863 yield (endsess, 1, end)
862
864
863
865
864 def _format_lineno(session, line):
866 def _format_lineno(session, line):
865 """Helper function to format line numbers properly."""
867 """Helper function to format line numbers properly."""
866 if session == 0:
868 if session == 0:
867 return str(line)
869 return str(line)
868 return "%s#%s" % (session, line)
870 return "%s#%s" % (session, line)
869
871
870
872
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,702 +1,702 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Magic functions for InteractiveShell.
2 """Magic functions for InteractiveShell.
3 """
3 """
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
8 # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
8 # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008 The IPython Development Team
10
10
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import types
22 import types
23 from getopt import getopt, GetoptError
23 from getopt import getopt, GetoptError
24
24
25 # Our own
25 # Our own
26 from IPython.config.configurable import Configurable
26 from IPython.config.configurable import Configurable
27 from IPython.core import oinspect
27 from IPython.core import oinspect
28 from IPython.core.error import UsageError
28 from IPython.core.error import UsageError
29 from IPython.core.inputsplitter import ESC_MAGIC, ESC_MAGIC2
29 from IPython.core.inputsplitter import ESC_MAGIC, ESC_MAGIC2
30 from decorator import decorator
30 from decorator import decorator
31 from IPython.utils.ipstruct import Struct
31 from IPython.utils.ipstruct import Struct
32 from IPython.utils.process import arg_split
32 from IPython.utils.process import arg_split
33 from IPython.utils.py3compat import string_types, iteritems
33 from IPython.utils.py3compat import string_types, iteritems
34 from IPython.utils.text import dedent
34 from IPython.utils.text import dedent
35 from IPython.utils.traitlets import Bool, Dict, Instance, MetaHasTraits
35 from IPython.utils.traitlets import Bool, Dict, Instance, MetaHasTraits
36 from IPython.utils.warn import error
36 from IPython.utils.warn import error
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Globals
39 # Globals
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 # A dict we'll use for each class that has magics, used as temporary storage to
42 # A dict we'll use for each class that has magics, used as temporary storage to
43 # pass information between the @line/cell_magic method decorators and the
43 # pass information between the @line/cell_magic method decorators and the
44 # @magics_class class decorator, because the method decorators have no
44 # @magics_class class decorator, because the method decorators have no
45 # access to the class when they run. See for more details:
45 # access to the class when they run. See for more details:
46 # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
46 # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
47
47
48 magics = dict(line={}, cell={})
48 magics = dict(line={}, cell={})
49
49
50 magic_kinds = ('line', 'cell')
50 magic_kinds = ('line', 'cell')
51 magic_spec = ('line', 'cell', 'line_cell')
51 magic_spec = ('line', 'cell', 'line_cell')
52 magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
52 magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Utility classes and functions
55 # Utility classes and functions
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 class Bunch: pass
58 class Bunch: pass
59
59
60
60
61 def on_off(tag):
61 def on_off(tag):
62 """Return an ON/OFF string for a 1/0 input. Simple utility function."""
62 """Return an ON/OFF string for a 1/0 input. Simple utility function."""
63 return ['OFF','ON'][tag]
63 return ['OFF','ON'][tag]
64
64
65
65
66 def compress_dhist(dh):
66 def compress_dhist(dh):
67 """Compress a directory history into a new one with at most 20 entries.
67 """Compress a directory history into a new one with at most 20 entries.
68
68
69 Return a new list made from the first and last 10 elements of dhist after
69 Return a new list made from the first and last 10 elements of dhist after
70 removal of duplicates.
70 removal of duplicates.
71 """
71 """
72 head, tail = dh[:-10], dh[-10:]
72 head, tail = dh[:-10], dh[-10:]
73
73
74 newhead = []
74 newhead = []
75 done = set()
75 done = set()
76 for h in head:
76 for h in head:
77 if h in done:
77 if h in done:
78 continue
78 continue
79 newhead.append(h)
79 newhead.append(h)
80 done.add(h)
80 done.add(h)
81
81
82 return newhead + tail
82 return newhead + tail
83
83
84
84
85 def needs_local_scope(func):
85 def needs_local_scope(func):
86 """Decorator to mark magic functions which need to local scope to run."""
86 """Decorator to mark magic functions which need to local scope to run."""
87 func.needs_local_scope = True
87 func.needs_local_scope = True
88 return func
88 return func
89
89
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91 # Class and method decorators for registering magics
91 # Class and method decorators for registering magics
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93
93
94 def magics_class(cls):
94 def magics_class(cls):
95 """Class decorator for all subclasses of the main Magics class.
95 """Class decorator for all subclasses of the main Magics class.
96
96
97 Any class that subclasses Magics *must* also apply this decorator, to
97 Any class that subclasses Magics *must* also apply this decorator, to
98 ensure that all the methods that have been decorated as line/cell magics
98 ensure that all the methods that have been decorated as line/cell magics
99 get correctly registered in the class instance. This is necessary because
99 get correctly registered in the class instance. This is necessary because
100 when method decorators run, the class does not exist yet, so they
100 when method decorators run, the class does not exist yet, so they
101 temporarily store their information into a module global. Application of
101 temporarily store their information into a module global. Application of
102 this class decorator copies that global data to the class instance and
102 this class decorator copies that global data to the class instance and
103 clears the global.
103 clears the global.
104
104
105 Obviously, this mechanism is not thread-safe, which means that the
105 Obviously, this mechanism is not thread-safe, which means that the
106 *creation* of subclasses of Magic should only be done in a single-thread
106 *creation* of subclasses of Magic should only be done in a single-thread
107 context. Instantiation of the classes has no restrictions. Given that
107 context. Instantiation of the classes has no restrictions. Given that
108 these classes are typically created at IPython startup time and before user
108 these classes are typically created at IPython startup time and before user
109 application code becomes active, in practice this should not pose any
109 application code becomes active, in practice this should not pose any
110 problems.
110 problems.
111 """
111 """
112 cls.registered = True
112 cls.registered = True
113 cls.magics = dict(line = magics['line'],
113 cls.magics = dict(line = magics['line'],
114 cell = magics['cell'])
114 cell = magics['cell'])
115 magics['line'] = {}
115 magics['line'] = {}
116 magics['cell'] = {}
116 magics['cell'] = {}
117 return cls
117 return cls
118
118
119
119
120 def record_magic(dct, magic_kind, magic_name, func):
120 def record_magic(dct, magic_kind, magic_name, func):
121 """Utility function to store a function as a magic of a specific kind.
121 """Utility function to store a function as a magic of a specific kind.
122
122
123 Parameters
123 Parameters
124 ----------
124 ----------
125 dct : dict
125 dct : dict
126 A dictionary with 'line' and 'cell' subdicts.
126 A dictionary with 'line' and 'cell' subdicts.
127
127
128 magic_kind : str
128 magic_kind : str
129 Kind of magic to be stored.
129 Kind of magic to be stored.
130
130
131 magic_name : str
131 magic_name : str
132 Key to store the magic as.
132 Key to store the magic as.
133
133
134 func : function
134 func : function
135 Callable object to store.
135 Callable object to store.
136 """
136 """
137 if magic_kind == 'line_cell':
137 if magic_kind == 'line_cell':
138 dct['line'][magic_name] = dct['cell'][magic_name] = func
138 dct['line'][magic_name] = dct['cell'][magic_name] = func
139 else:
139 else:
140 dct[magic_kind][magic_name] = func
140 dct[magic_kind][magic_name] = func
141
141
142
142
143 def validate_type(magic_kind):
143 def validate_type(magic_kind):
144 """Ensure that the given magic_kind is valid.
144 """Ensure that the given magic_kind is valid.
145
145
146 Check that the given magic_kind is one of the accepted spec types (stored
146 Check that the given magic_kind is one of the accepted spec types (stored
147 in the global `magic_spec`), raise ValueError otherwise.
147 in the global `magic_spec`), raise ValueError otherwise.
148 """
148 """
149 if magic_kind not in magic_spec:
149 if magic_kind not in magic_spec:
150 raise ValueError('magic_kind must be one of %s, %s given' %
150 raise ValueError('magic_kind must be one of %s, %s given' %
151 magic_kinds, magic_kind)
151 magic_kinds, magic_kind)
152
152
153
153
154 # The docstrings for the decorator below will be fairly similar for the two
154 # The docstrings for the decorator below will be fairly similar for the two
155 # types (method and function), so we generate them here once and reuse the
155 # types (method and function), so we generate them here once and reuse the
156 # templates below.
156 # templates below.
157 _docstring_template = \
157 _docstring_template = \
158 """Decorate the given {0} as {1} magic.
158 """Decorate the given {0} as {1} magic.
159
159
160 The decorator can be used with or without arguments, as follows.
160 The decorator can be used with or without arguments, as follows.
161
161
162 i) without arguments: it will create a {1} magic named as the {0} being
162 i) without arguments: it will create a {1} magic named as the {0} being
163 decorated::
163 decorated::
164
164
165 @deco
165 @deco
166 def foo(...)
166 def foo(...)
167
167
168 will create a {1} magic named `foo`.
168 will create a {1} magic named `foo`.
169
169
170 ii) with one string argument: which will be used as the actual name of the
170 ii) with one string argument: which will be used as the actual name of the
171 resulting magic::
171 resulting magic::
172
172
173 @deco('bar')
173 @deco('bar')
174 def foo(...)
174 def foo(...)
175
175
176 will create a {1} magic named `bar`.
176 will create a {1} magic named `bar`.
177 """
177 """
178
178
179 # These two are decorator factories. While they are conceptually very similar,
179 # These two are decorator factories. While they are conceptually very similar,
180 # there are enough differences in the details that it's simpler to have them
180 # there are enough differences in the details that it's simpler to have them
181 # written as completely standalone functions rather than trying to share code
181 # written as completely standalone functions rather than trying to share code
182 # and make a single one with convoluted logic.
182 # and make a single one with convoluted logic.
183
183
184 def _method_magic_marker(magic_kind):
184 def _method_magic_marker(magic_kind):
185 """Decorator factory for methods in Magics subclasses.
185 """Decorator factory for methods in Magics subclasses.
186 """
186 """
187
187
188 validate_type(magic_kind)
188 validate_type(magic_kind)
189
189
190 # This is a closure to capture the magic_kind. We could also use a class,
190 # This is a closure to capture the magic_kind. We could also use a class,
191 # but it's overkill for just that one bit of state.
191 # but it's overkill for just that one bit of state.
192 def magic_deco(arg):
192 def magic_deco(arg):
193 call = lambda f, *a, **k: f(*a, **k)
193 call = lambda f, *a, **k: f(*a, **k)
194
194
195 if callable(arg):
195 if callable(arg):
196 # "Naked" decorator call (just @foo, no args)
196 # "Naked" decorator call (just @foo, no args)
197 func = arg
197 func = arg
198 name = func.__name__
198 name = func.__name__
199 retval = decorator(call, func)
199 retval = decorator(call, func)
200 record_magic(magics, magic_kind, name, name)
200 record_magic(magics, magic_kind, name, name)
201 elif isinstance(arg, string_types):
201 elif isinstance(arg, string_types):
202 # Decorator called with arguments (@foo('bar'))
202 # Decorator called with arguments (@foo('bar'))
203 name = arg
203 name = arg
204 def mark(func, *a, **kw):
204 def mark(func, *a, **kw):
205 record_magic(magics, magic_kind, name, func.__name__)
205 record_magic(magics, magic_kind, name, func.__name__)
206 return decorator(call, func)
206 return decorator(call, func)
207 retval = mark
207 retval = mark
208 else:
208 else:
209 raise TypeError("Decorator can only be called with "
209 raise TypeError("Decorator can only be called with "
210 "string or function")
210 "string or function")
211 return retval
211 return retval
212
212
213 # Ensure the resulting decorator has a usable docstring
213 # Ensure the resulting decorator has a usable docstring
214 magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
214 magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
215 return magic_deco
215 return magic_deco
216
216
217
217
218 def _function_magic_marker(magic_kind):
218 def _function_magic_marker(magic_kind):
219 """Decorator factory for standalone functions.
219 """Decorator factory for standalone functions.
220 """
220 """
221 validate_type(magic_kind)
221 validate_type(magic_kind)
222
222
223 # This is a closure to capture the magic_kind. We could also use a class,
223 # This is a closure to capture the magic_kind. We could also use a class,
224 # but it's overkill for just that one bit of state.
224 # but it's overkill for just that one bit of state.
225 def magic_deco(arg):
225 def magic_deco(arg):
226 call = lambda f, *a, **k: f(*a, **k)
226 call = lambda f, *a, **k: f(*a, **k)
227
227
228 # Find get_ipython() in the caller's namespace
228 # Find get_ipython() in the caller's namespace
229 caller = sys._getframe(1)
229 caller = sys._getframe(1)
230 for ns in ['f_locals', 'f_globals', 'f_builtins']:
230 for ns in ['f_locals', 'f_globals', 'f_builtins']:
231 get_ipython = getattr(caller, ns).get('get_ipython')
231 get_ipython = getattr(caller, ns).get('get_ipython')
232 if get_ipython is not None:
232 if get_ipython is not None:
233 break
233 break
234 else:
234 else:
235 raise NameError('Decorator can only run in context where '
235 raise NameError('Decorator can only run in context where '
236 '`get_ipython` exists')
236 '`get_ipython` exists')
237
237
238 ip = get_ipython()
238 ip = get_ipython()
239
239
240 if callable(arg):
240 if callable(arg):
241 # "Naked" decorator call (just @foo, no args)
241 # "Naked" decorator call (just @foo, no args)
242 func = arg
242 func = arg
243 name = func.__name__
243 name = func.__name__
244 ip.register_magic_function(func, magic_kind, name)
244 ip.register_magic_function(func, magic_kind, name)
245 retval = decorator(call, func)
245 retval = decorator(call, func)
246 elif isinstance(arg, string_types):
246 elif isinstance(arg, string_types):
247 # Decorator called with arguments (@foo('bar'))
247 # Decorator called with arguments (@foo('bar'))
248 name = arg
248 name = arg
249 def mark(func, *a, **kw):
249 def mark(func, *a, **kw):
250 ip.register_magic_function(func, magic_kind, name)
250 ip.register_magic_function(func, magic_kind, name)
251 return decorator(call, func)
251 return decorator(call, func)
252 retval = mark
252 retval = mark
253 else:
253 else:
254 raise TypeError("Decorator can only be called with "
254 raise TypeError("Decorator can only be called with "
255 "string or function")
255 "string or function")
256 return retval
256 return retval
257
257
258 # Ensure the resulting decorator has a usable docstring
258 # Ensure the resulting decorator has a usable docstring
259 ds = _docstring_template.format('function', magic_kind)
259 ds = _docstring_template.format('function', magic_kind)
260
260
261 ds += dedent("""
261 ds += dedent("""
262 Note: this decorator can only be used in a context where IPython is already
262 Note: this decorator can only be used in a context where IPython is already
263 active, so that the `get_ipython()` call succeeds. You can therefore use
263 active, so that the `get_ipython()` call succeeds. You can therefore use
264 it in your startup files loaded after IPython initializes, but *not* in the
264 it in your startup files loaded after IPython initializes, but *not* in the
265 IPython configuration file itself, which is executed before IPython is
265 IPython configuration file itself, which is executed before IPython is
266 fully up and running. Any file located in the `startup` subdirectory of
266 fully up and running. Any file located in the `startup` subdirectory of
267 your configuration profile will be OK in this sense.
267 your configuration profile will be OK in this sense.
268 """)
268 """)
269
269
270 magic_deco.__doc__ = ds
270 magic_deco.__doc__ = ds
271 return magic_deco
271 return magic_deco
272
272
273
273
274 # Create the actual decorators for public use
274 # Create the actual decorators for public use
275
275
276 # These three are used to decorate methods in class definitions
276 # These three are used to decorate methods in class definitions
277 line_magic = _method_magic_marker('line')
277 line_magic = _method_magic_marker('line')
278 cell_magic = _method_magic_marker('cell')
278 cell_magic = _method_magic_marker('cell')
279 line_cell_magic = _method_magic_marker('line_cell')
279 line_cell_magic = _method_magic_marker('line_cell')
280
280
281 # These three decorate standalone functions and perform the decoration
281 # These three decorate standalone functions and perform the decoration
282 # immediately. They can only run where get_ipython() works
282 # immediately. They can only run where get_ipython() works
283 register_line_magic = _function_magic_marker('line')
283 register_line_magic = _function_magic_marker('line')
284 register_cell_magic = _function_magic_marker('cell')
284 register_cell_magic = _function_magic_marker('cell')
285 register_line_cell_magic = _function_magic_marker('line_cell')
285 register_line_cell_magic = _function_magic_marker('line_cell')
286
286
287 #-----------------------------------------------------------------------------
287 #-----------------------------------------------------------------------------
288 # Core Magic classes
288 # Core Magic classes
289 #-----------------------------------------------------------------------------
289 #-----------------------------------------------------------------------------
290
290
291 class MagicsManager(Configurable):
291 class MagicsManager(Configurable):
292 """Object that handles all magic-related functionality for IPython.
292 """Object that handles all magic-related functionality for IPython.
293 """
293 """
294 # Non-configurable class attributes
294 # Non-configurable class attributes
295
295
296 # A two-level dict, first keyed by magic type, then by magic function, and
296 # A two-level dict, first keyed by magic type, then by magic function, and
297 # holding the actual callable object as value. This is the dict used for
297 # holding the actual callable object as value. This is the dict used for
298 # magic function dispatch
298 # magic function dispatch
299 magics = Dict
299 magics = Dict
300
300
301 # A registry of the original objects that we've been given holding magics.
301 # A registry of the original objects that we've been given holding magics.
302 registry = Dict
302 registry = Dict
303
303
304 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
304 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
305
305
306 auto_magic = Bool(True, config=True, help=
306 auto_magic = Bool(True, config=True, help=
307 "Automatically call line magics without requiring explicit % prefix")
307 "Automatically call line magics without requiring explicit % prefix")
308
308
309 def _auto_magic_changed(self, name, value):
309 def _auto_magic_changed(self, name, value):
310 self.shell.automagic = value
310 self.shell.automagic = value
311
311
312 _auto_status = [
312 _auto_status = [
313 'Automagic is OFF, % prefix IS needed for line magics.',
313 'Automagic is OFF, % prefix IS needed for line magics.',
314 'Automagic is ON, % prefix IS NOT needed for line magics.']
314 'Automagic is ON, % prefix IS NOT needed for line magics.']
315
315
316 user_magics = Instance('IPython.core.magics.UserMagics')
316 user_magics = Instance('IPython.core.magics.UserMagics', allow_none=True)
317
317
318 def __init__(self, shell=None, config=None, user_magics=None, **traits):
318 def __init__(self, shell=None, config=None, user_magics=None, **traits):
319
319
320 super(MagicsManager, self).__init__(shell=shell, config=config,
320 super(MagicsManager, self).__init__(shell=shell, config=config,
321 user_magics=user_magics, **traits)
321 user_magics=user_magics, **traits)
322 self.magics = dict(line={}, cell={})
322 self.magics = dict(line={}, cell={})
323 # Let's add the user_magics to the registry for uniformity, so *all*
323 # Let's add the user_magics to the registry for uniformity, so *all*
324 # registered magic containers can be found there.
324 # registered magic containers can be found there.
325 self.registry[user_magics.__class__.__name__] = user_magics
325 self.registry[user_magics.__class__.__name__] = user_magics
326
326
327 def auto_status(self):
327 def auto_status(self):
328 """Return descriptive string with automagic status."""
328 """Return descriptive string with automagic status."""
329 return self._auto_status[self.auto_magic]
329 return self._auto_status[self.auto_magic]
330
330
331 def lsmagic(self):
331 def lsmagic(self):
332 """Return a dict of currently available magic functions.
332 """Return a dict of currently available magic functions.
333
333
334 The return dict has the keys 'line' and 'cell', corresponding to the
334 The return dict has the keys 'line' and 'cell', corresponding to the
335 two types of magics we support. Each value is a list of names.
335 two types of magics we support. Each value is a list of names.
336 """
336 """
337 return self.magics
337 return self.magics
338
338
339 def lsmagic_docs(self, brief=False, missing=''):
339 def lsmagic_docs(self, brief=False, missing=''):
340 """Return dict of documentation of magic functions.
340 """Return dict of documentation of magic functions.
341
341
342 The return dict has the keys 'line' and 'cell', corresponding to the
342 The return dict has the keys 'line' and 'cell', corresponding to the
343 two types of magics we support. Each value is a dict keyed by magic
343 two types of magics we support. Each value is a dict keyed by magic
344 name whose value is the function docstring. If a docstring is
344 name whose value is the function docstring. If a docstring is
345 unavailable, the value of `missing` is used instead.
345 unavailable, the value of `missing` is used instead.
346
346
347 If brief is True, only the first line of each docstring will be returned.
347 If brief is True, only the first line of each docstring will be returned.
348 """
348 """
349 docs = {}
349 docs = {}
350 for m_type in self.magics:
350 for m_type in self.magics:
351 m_docs = {}
351 m_docs = {}
352 for m_name, m_func in iteritems(self.magics[m_type]):
352 for m_name, m_func in iteritems(self.magics[m_type]):
353 if m_func.__doc__:
353 if m_func.__doc__:
354 if brief:
354 if brief:
355 m_docs[m_name] = m_func.__doc__.split('\n', 1)[0]
355 m_docs[m_name] = m_func.__doc__.split('\n', 1)[0]
356 else:
356 else:
357 m_docs[m_name] = m_func.__doc__.rstrip()
357 m_docs[m_name] = m_func.__doc__.rstrip()
358 else:
358 else:
359 m_docs[m_name] = missing
359 m_docs[m_name] = missing
360 docs[m_type] = m_docs
360 docs[m_type] = m_docs
361 return docs
361 return docs
362
362
363 def register(self, *magic_objects):
363 def register(self, *magic_objects):
364 """Register one or more instances of Magics.
364 """Register one or more instances of Magics.
365
365
366 Take one or more classes or instances of classes that subclass the main
366 Take one or more classes or instances of classes that subclass the main
367 `core.Magic` class, and register them with IPython to use the magic
367 `core.Magic` class, and register them with IPython to use the magic
368 functions they provide. The registration process will then ensure that
368 functions they provide. The registration process will then ensure that
369 any methods that have decorated to provide line and/or cell magics will
369 any methods that have decorated to provide line and/or cell magics will
370 be recognized with the `%x`/`%%x` syntax as a line/cell magic
370 be recognized with the `%x`/`%%x` syntax as a line/cell magic
371 respectively.
371 respectively.
372
372
373 If classes are given, they will be instantiated with the default
373 If classes are given, they will be instantiated with the default
374 constructor. If your classes need a custom constructor, you should
374 constructor. If your classes need a custom constructor, you should
375 instanitate them first and pass the instance.
375 instanitate them first and pass the instance.
376
376
377 The provided arguments can be an arbitrary mix of classes and instances.
377 The provided arguments can be an arbitrary mix of classes and instances.
378
378
379 Parameters
379 Parameters
380 ----------
380 ----------
381 magic_objects : one or more classes or instances
381 magic_objects : one or more classes or instances
382 """
382 """
383 # Start by validating them to ensure they have all had their magic
383 # Start by validating them to ensure they have all had their magic
384 # methods registered at the instance level
384 # methods registered at the instance level
385 for m in magic_objects:
385 for m in magic_objects:
386 if not m.registered:
386 if not m.registered:
387 raise ValueError("Class of magics %r was constructed without "
387 raise ValueError("Class of magics %r was constructed without "
388 "the @register_magics class decorator")
388 "the @register_magics class decorator")
389 if type(m) in (type, MetaHasTraits):
389 if type(m) in (type, MetaHasTraits):
390 # If we're given an uninstantiated class
390 # If we're given an uninstantiated class
391 m = m(shell=self.shell)
391 m = m(shell=self.shell)
392
392
393 # Now that we have an instance, we can register it and update the
393 # Now that we have an instance, we can register it and update the
394 # table of callables
394 # table of callables
395 self.registry[m.__class__.__name__] = m
395 self.registry[m.__class__.__name__] = m
396 for mtype in magic_kinds:
396 for mtype in magic_kinds:
397 self.magics[mtype].update(m.magics[mtype])
397 self.magics[mtype].update(m.magics[mtype])
398
398
399 def register_function(self, func, magic_kind='line', magic_name=None):
399 def register_function(self, func, magic_kind='line', magic_name=None):
400 """Expose a standalone function as magic function for IPython.
400 """Expose a standalone function as magic function for IPython.
401
401
402 This will create an IPython magic (line, cell or both) from a
402 This will create an IPython magic (line, cell or both) from a
403 standalone function. The functions should have the following
403 standalone function. The functions should have the following
404 signatures:
404 signatures:
405
405
406 * For line magics: `def f(line)`
406 * For line magics: `def f(line)`
407 * For cell magics: `def f(line, cell)`
407 * For cell magics: `def f(line, cell)`
408 * For a function that does both: `def f(line, cell=None)`
408 * For a function that does both: `def f(line, cell=None)`
409
409
410 In the latter case, the function will be called with `cell==None` when
410 In the latter case, the function will be called with `cell==None` when
411 invoked as `%f`, and with cell as a string when invoked as `%%f`.
411 invoked as `%f`, and with cell as a string when invoked as `%%f`.
412
412
413 Parameters
413 Parameters
414 ----------
414 ----------
415 func : callable
415 func : callable
416 Function to be registered as a magic.
416 Function to be registered as a magic.
417
417
418 magic_kind : str
418 magic_kind : str
419 Kind of magic, one of 'line', 'cell' or 'line_cell'
419 Kind of magic, one of 'line', 'cell' or 'line_cell'
420
420
421 magic_name : optional str
421 magic_name : optional str
422 If given, the name the magic will have in the IPython namespace. By
422 If given, the name the magic will have in the IPython namespace. By
423 default, the name of the function itself is used.
423 default, the name of the function itself is used.
424 """
424 """
425
425
426 # Create the new method in the user_magics and register it in the
426 # Create the new method in the user_magics and register it in the
427 # global table
427 # global table
428 validate_type(magic_kind)
428 validate_type(magic_kind)
429 magic_name = func.__name__ if magic_name is None else magic_name
429 magic_name = func.__name__ if magic_name is None else magic_name
430 setattr(self.user_magics, magic_name, func)
430 setattr(self.user_magics, magic_name, func)
431 record_magic(self.magics, magic_kind, magic_name, func)
431 record_magic(self.magics, magic_kind, magic_name, func)
432
432
433 def define_magic(self, name, func):
433 def define_magic(self, name, func):
434 """[Deprecated] Expose own function as magic function for IPython.
434 """[Deprecated] Expose own function as magic function for IPython.
435
435
436 Example::
436 Example::
437
437
438 def foo_impl(self, parameter_s=''):
438 def foo_impl(self, parameter_s=''):
439 'My very own magic!. (Use docstrings, IPython reads them).'
439 'My very own magic!. (Use docstrings, IPython reads them).'
440 print 'Magic function. Passed parameter is between < >:'
440 print 'Magic function. Passed parameter is between < >:'
441 print '<%s>' % parameter_s
441 print '<%s>' % parameter_s
442 print 'The self object is:', self
442 print 'The self object is:', self
443
443
444 ip.define_magic('foo',foo_impl)
444 ip.define_magic('foo',foo_impl)
445 """
445 """
446 meth = types.MethodType(func, self.user_magics)
446 meth = types.MethodType(func, self.user_magics)
447 setattr(self.user_magics, name, meth)
447 setattr(self.user_magics, name, meth)
448 record_magic(self.magics, 'line', name, meth)
448 record_magic(self.magics, 'line', name, meth)
449
449
450 def register_alias(self, alias_name, magic_name, magic_kind='line'):
450 def register_alias(self, alias_name, magic_name, magic_kind='line'):
451 """Register an alias to a magic function.
451 """Register an alias to a magic function.
452
452
453 The alias is an instance of :class:`MagicAlias`, which holds the
453 The alias is an instance of :class:`MagicAlias`, which holds the
454 name and kind of the magic it should call. Binding is done at
454 name and kind of the magic it should call. Binding is done at
455 call time, so if the underlying magic function is changed the alias
455 call time, so if the underlying magic function is changed the alias
456 will call the new function.
456 will call the new function.
457
457
458 Parameters
458 Parameters
459 ----------
459 ----------
460 alias_name : str
460 alias_name : str
461 The name of the magic to be registered.
461 The name of the magic to be registered.
462
462
463 magic_name : str
463 magic_name : str
464 The name of an existing magic.
464 The name of an existing magic.
465
465
466 magic_kind : str
466 magic_kind : str
467 Kind of magic, one of 'line' or 'cell'
467 Kind of magic, one of 'line' or 'cell'
468 """
468 """
469
469
470 # `validate_type` is too permissive, as it allows 'line_cell'
470 # `validate_type` is too permissive, as it allows 'line_cell'
471 # which we do not handle.
471 # which we do not handle.
472 if magic_kind not in magic_kinds:
472 if magic_kind not in magic_kinds:
473 raise ValueError('magic_kind must be one of %s, %s given' %
473 raise ValueError('magic_kind must be one of %s, %s given' %
474 magic_kinds, magic_kind)
474 magic_kinds, magic_kind)
475
475
476 alias = MagicAlias(self.shell, magic_name, magic_kind)
476 alias = MagicAlias(self.shell, magic_name, magic_kind)
477 setattr(self.user_magics, alias_name, alias)
477 setattr(self.user_magics, alias_name, alias)
478 record_magic(self.magics, magic_kind, alias_name, alias)
478 record_magic(self.magics, magic_kind, alias_name, alias)
479
479
480 # Key base class that provides the central functionality for magics.
480 # Key base class that provides the central functionality for magics.
481
481
482
482
483 class Magics(Configurable):
483 class Magics(Configurable):
484 """Base class for implementing magic functions.
484 """Base class for implementing magic functions.
485
485
486 Shell functions which can be reached as %function_name. All magic
486 Shell functions which can be reached as %function_name. All magic
487 functions should accept a string, which they can parse for their own
487 functions should accept a string, which they can parse for their own
488 needs. This can make some functions easier to type, eg `%cd ../`
488 needs. This can make some functions easier to type, eg `%cd ../`
489 vs. `%cd("../")`
489 vs. `%cd("../")`
490
490
491 Classes providing magic functions need to subclass this class, and they
491 Classes providing magic functions need to subclass this class, and they
492 MUST:
492 MUST:
493
493
494 - Use the method decorators `@line_magic` and `@cell_magic` to decorate
494 - Use the method decorators `@line_magic` and `@cell_magic` to decorate
495 individual methods as magic functions, AND
495 individual methods as magic functions, AND
496
496
497 - Use the class decorator `@magics_class` to ensure that the magic
497 - Use the class decorator `@magics_class` to ensure that the magic
498 methods are properly registered at the instance level upon instance
498 methods are properly registered at the instance level upon instance
499 initialization.
499 initialization.
500
500
501 See :mod:`magic_functions` for examples of actual implementation classes.
501 See :mod:`magic_functions` for examples of actual implementation classes.
502 """
502 """
503 # Dict holding all command-line options for each magic.
503 # Dict holding all command-line options for each magic.
504 options_table = None
504 options_table = None
505 # Dict for the mapping of magic names to methods, set by class decorator
505 # Dict for the mapping of magic names to methods, set by class decorator
506 magics = None
506 magics = None
507 # Flag to check that the class decorator was properly applied
507 # Flag to check that the class decorator was properly applied
508 registered = False
508 registered = False
509 # Instance of IPython shell
509 # Instance of IPython shell
510 shell = None
510 shell = None
511
511
512 def __init__(self, shell=None, **kwargs):
512 def __init__(self, shell=None, **kwargs):
513 if not(self.__class__.registered):
513 if not(self.__class__.registered):
514 raise ValueError('Magics subclass without registration - '
514 raise ValueError('Magics subclass without registration - '
515 'did you forget to apply @magics_class?')
515 'did you forget to apply @magics_class?')
516 if shell is not None:
516 if shell is not None:
517 if hasattr(shell, 'configurables'):
517 if hasattr(shell, 'configurables'):
518 shell.configurables.append(self)
518 shell.configurables.append(self)
519 if hasattr(shell, 'config'):
519 if hasattr(shell, 'config'):
520 kwargs.setdefault('parent', shell)
520 kwargs.setdefault('parent', shell)
521 kwargs['shell'] = shell
521 kwargs['shell'] = shell
522
522
523 self.shell = shell
523 self.shell = shell
524 self.options_table = {}
524 self.options_table = {}
525 # The method decorators are run when the instance doesn't exist yet, so
525 # The method decorators are run when the instance doesn't exist yet, so
526 # they can only record the names of the methods they are supposed to
526 # they can only record the names of the methods they are supposed to
527 # grab. Only now, that the instance exists, can we create the proper
527 # grab. Only now, that the instance exists, can we create the proper
528 # mapping to bound methods. So we read the info off the original names
528 # mapping to bound methods. So we read the info off the original names
529 # table and replace each method name by the actual bound method.
529 # table and replace each method name by the actual bound method.
530 # But we mustn't clobber the *class* mapping, in case of multiple instances.
530 # But we mustn't clobber the *class* mapping, in case of multiple instances.
531 class_magics = self.magics
531 class_magics = self.magics
532 self.magics = {}
532 self.magics = {}
533 for mtype in magic_kinds:
533 for mtype in magic_kinds:
534 tab = self.magics[mtype] = {}
534 tab = self.magics[mtype] = {}
535 cls_tab = class_magics[mtype]
535 cls_tab = class_magics[mtype]
536 for magic_name, meth_name in iteritems(cls_tab):
536 for magic_name, meth_name in iteritems(cls_tab):
537 if isinstance(meth_name, string_types):
537 if isinstance(meth_name, string_types):
538 # it's a method name, grab it
538 # it's a method name, grab it
539 tab[magic_name] = getattr(self, meth_name)
539 tab[magic_name] = getattr(self, meth_name)
540 else:
540 else:
541 # it's the real thing
541 # it's the real thing
542 tab[magic_name] = meth_name
542 tab[magic_name] = meth_name
543 # Configurable **needs** to be initiated at the end or the config
543 # Configurable **needs** to be initiated at the end or the config
544 # magics get screwed up.
544 # magics get screwed up.
545 super(Magics, self).__init__(**kwargs)
545 super(Magics, self).__init__(**kwargs)
546
546
547 def arg_err(self,func):
547 def arg_err(self,func):
548 """Print docstring if incorrect arguments were passed"""
548 """Print docstring if incorrect arguments were passed"""
549 print('Error in arguments:')
549 print('Error in arguments:')
550 print(oinspect.getdoc(func))
550 print(oinspect.getdoc(func))
551
551
552 def format_latex(self, strng):
552 def format_latex(self, strng):
553 """Format a string for latex inclusion."""
553 """Format a string for latex inclusion."""
554
554
555 # Characters that need to be escaped for latex:
555 # Characters that need to be escaped for latex:
556 escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
556 escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
557 # Magic command names as headers:
557 # Magic command names as headers:
558 cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
558 cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
559 re.MULTILINE)
559 re.MULTILINE)
560 # Magic commands
560 # Magic commands
561 cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
561 cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
562 re.MULTILINE)
562 re.MULTILINE)
563 # Paragraph continue
563 # Paragraph continue
564 par_re = re.compile(r'\\$',re.MULTILINE)
564 par_re = re.compile(r'\\$',re.MULTILINE)
565
565
566 # The "\n" symbol
566 # The "\n" symbol
567 newline_re = re.compile(r'\\n')
567 newline_re = re.compile(r'\\n')
568
568
569 # Now build the string for output:
569 # Now build the string for output:
570 #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
570 #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
571 strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
571 strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
572 strng)
572 strng)
573 strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
573 strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
574 strng = par_re.sub(r'\\\\',strng)
574 strng = par_re.sub(r'\\\\',strng)
575 strng = escape_re.sub(r'\\\1',strng)
575 strng = escape_re.sub(r'\\\1',strng)
576 strng = newline_re.sub(r'\\textbackslash{}n',strng)
576 strng = newline_re.sub(r'\\textbackslash{}n',strng)
577 return strng
577 return strng
578
578
579 def parse_options(self, arg_str, opt_str, *long_opts, **kw):
579 def parse_options(self, arg_str, opt_str, *long_opts, **kw):
580 """Parse options passed to an argument string.
580 """Parse options passed to an argument string.
581
581
582 The interface is similar to that of :func:`getopt.getopt`, but it
582 The interface is similar to that of :func:`getopt.getopt`, but it
583 returns a :class:`~IPython.utils.struct.Struct` with the options as keys
583 returns a :class:`~IPython.utils.struct.Struct` with the options as keys
584 and the stripped argument string still as a string.
584 and the stripped argument string still as a string.
585
585
586 arg_str is quoted as a true sys.argv vector by using shlex.split.
586 arg_str is quoted as a true sys.argv vector by using shlex.split.
587 This allows us to easily expand variables, glob files, quote
587 This allows us to easily expand variables, glob files, quote
588 arguments, etc.
588 arguments, etc.
589
589
590 Parameters
590 Parameters
591 ----------
591 ----------
592
592
593 arg_str : str
593 arg_str : str
594 The arguments to parse.
594 The arguments to parse.
595
595
596 opt_str : str
596 opt_str : str
597 The options specification.
597 The options specification.
598
598
599 mode : str, default 'string'
599 mode : str, default 'string'
600 If given as 'list', the argument string is returned as a list (split
600 If given as 'list', the argument string is returned as a list (split
601 on whitespace) instead of a string.
601 on whitespace) instead of a string.
602
602
603 list_all : bool, default False
603 list_all : bool, default False
604 Put all option values in lists. Normally only options
604 Put all option values in lists. Normally only options
605 appearing more than once are put in a list.
605 appearing more than once are put in a list.
606
606
607 posix : bool, default True
607 posix : bool, default True
608 Whether to split the input line in POSIX mode or not, as per the
608 Whether to split the input line in POSIX mode or not, as per the
609 conventions outlined in the :mod:`shlex` module from the standard
609 conventions outlined in the :mod:`shlex` module from the standard
610 library.
610 library.
611 """
611 """
612
612
613 # inject default options at the beginning of the input line
613 # inject default options at the beginning of the input line
614 caller = sys._getframe(1).f_code.co_name
614 caller = sys._getframe(1).f_code.co_name
615 arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
615 arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
616
616
617 mode = kw.get('mode','string')
617 mode = kw.get('mode','string')
618 if mode not in ['string','list']:
618 if mode not in ['string','list']:
619 raise ValueError('incorrect mode given: %s' % mode)
619 raise ValueError('incorrect mode given: %s' % mode)
620 # Get options
620 # Get options
621 list_all = kw.get('list_all',0)
621 list_all = kw.get('list_all',0)
622 posix = kw.get('posix', os.name == 'posix')
622 posix = kw.get('posix', os.name == 'posix')
623 strict = kw.get('strict', True)
623 strict = kw.get('strict', True)
624
624
625 # Check if we have more than one argument to warrant extra processing:
625 # Check if we have more than one argument to warrant extra processing:
626 odict = {} # Dictionary with options
626 odict = {} # Dictionary with options
627 args = arg_str.split()
627 args = arg_str.split()
628 if len(args) >= 1:
628 if len(args) >= 1:
629 # If the list of inputs only has 0 or 1 thing in it, there's no
629 # If the list of inputs only has 0 or 1 thing in it, there's no
630 # need to look for options
630 # need to look for options
631 argv = arg_split(arg_str, posix, strict)
631 argv = arg_split(arg_str, posix, strict)
632 # Do regular option processing
632 # Do regular option processing
633 try:
633 try:
634 opts,args = getopt(argv, opt_str, long_opts)
634 opts,args = getopt(argv, opt_str, long_opts)
635 except GetoptError as e:
635 except GetoptError as e:
636 raise UsageError('%s ( allowed: "%s" %s)' % (e.msg,opt_str,
636 raise UsageError('%s ( allowed: "%s" %s)' % (e.msg,opt_str,
637 " ".join(long_opts)))
637 " ".join(long_opts)))
638 for o,a in opts:
638 for o,a in opts:
639 if o.startswith('--'):
639 if o.startswith('--'):
640 o = o[2:]
640 o = o[2:]
641 else:
641 else:
642 o = o[1:]
642 o = o[1:]
643 try:
643 try:
644 odict[o].append(a)
644 odict[o].append(a)
645 except AttributeError:
645 except AttributeError:
646 odict[o] = [odict[o],a]
646 odict[o] = [odict[o],a]
647 except KeyError:
647 except KeyError:
648 if list_all:
648 if list_all:
649 odict[o] = [a]
649 odict[o] = [a]
650 else:
650 else:
651 odict[o] = a
651 odict[o] = a
652
652
653 # Prepare opts,args for return
653 # Prepare opts,args for return
654 opts = Struct(odict)
654 opts = Struct(odict)
655 if mode == 'string':
655 if mode == 'string':
656 args = ' '.join(args)
656 args = ' '.join(args)
657
657
658 return opts,args
658 return opts,args
659
659
660 def default_option(self, fn, optstr):
660 def default_option(self, fn, optstr):
661 """Make an entry in the options_table for fn, with value optstr"""
661 """Make an entry in the options_table for fn, with value optstr"""
662
662
663 if fn not in self.lsmagic():
663 if fn not in self.lsmagic():
664 error("%s is not a magic function" % fn)
664 error("%s is not a magic function" % fn)
665 self.options_table[fn] = optstr
665 self.options_table[fn] = optstr
666
666
667
667
668 class MagicAlias(object):
668 class MagicAlias(object):
669 """An alias to another magic function.
669 """An alias to another magic function.
670
670
671 An alias is determined by its magic name and magic kind. Lookup
671 An alias is determined by its magic name and magic kind. Lookup
672 is done at call time, so if the underlying magic changes the alias
672 is done at call time, so if the underlying magic changes the alias
673 will call the new function.
673 will call the new function.
674
674
675 Use the :meth:`MagicsManager.register_alias` method or the
675 Use the :meth:`MagicsManager.register_alias` method or the
676 `%alias_magic` magic function to create and register a new alias.
676 `%alias_magic` magic function to create and register a new alias.
677 """
677 """
678 def __init__(self, shell, magic_name, magic_kind):
678 def __init__(self, shell, magic_name, magic_kind):
679 self.shell = shell
679 self.shell = shell
680 self.magic_name = magic_name
680 self.magic_name = magic_name
681 self.magic_kind = magic_kind
681 self.magic_kind = magic_kind
682
682
683 self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name)
683 self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name)
684 self.__doc__ = "Alias for `%s`." % self.pretty_target
684 self.__doc__ = "Alias for `%s`." % self.pretty_target
685
685
686 self._in_call = False
686 self._in_call = False
687
687
688 def __call__(self, *args, **kwargs):
688 def __call__(self, *args, **kwargs):
689 """Call the magic alias."""
689 """Call the magic alias."""
690 fn = self.shell.find_magic(self.magic_name, self.magic_kind)
690 fn = self.shell.find_magic(self.magic_name, self.magic_kind)
691 if fn is None:
691 if fn is None:
692 raise UsageError("Magic `%s` not found." % self.pretty_target)
692 raise UsageError("Magic `%s` not found." % self.pretty_target)
693
693
694 # Protect against infinite recursion.
694 # Protect against infinite recursion.
695 if self._in_call:
695 if self._in_call:
696 raise UsageError("Infinite recursion detected; "
696 raise UsageError("Infinite recursion detected; "
697 "magic aliases cannot call themselves.")
697 "magic aliases cannot call themselves.")
698 self._in_call = True
698 self._in_call = True
699 try:
699 try:
700 return fn(*args, **kwargs)
700 return fn(*args, **kwargs)
701 finally:
701 finally:
702 self._in_call = False
702 self._in_call = False
@@ -1,715 +1,715 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Prefiltering components.
3 Prefiltering components.
4
4
5 Prefilters transform user input before it is exec'd by Python. These
5 Prefilters transform user input before it is exec'd by Python. These
6 transforms are used to implement additional syntax such as !ls and %magic.
6 transforms are used to implement additional syntax such as !ls and %magic.
7
7
8 Authors:
8 Authors:
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Dan Milstein
12 * Dan Milstein
13 * Ville Vainio
13 * Ville Vainio
14 """
14 """
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008-2011 The IPython Development Team
17 # Copyright (C) 2008-2011 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 from keyword import iskeyword
27 from keyword import iskeyword
28 import re
28 import re
29
29
30 from IPython.core.autocall import IPyAutocall
30 from IPython.core.autocall import IPyAutocall
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
32 from IPython.core.inputsplitter import (
32 from IPython.core.inputsplitter import (
33 ESC_MAGIC,
33 ESC_MAGIC,
34 ESC_QUOTE,
34 ESC_QUOTE,
35 ESC_QUOTE2,
35 ESC_QUOTE2,
36 ESC_PAREN,
36 ESC_PAREN,
37 )
37 )
38 from IPython.core.macro import Macro
38 from IPython.core.macro import Macro
39 from IPython.core.splitinput import LineInfo
39 from IPython.core.splitinput import LineInfo
40
40
41 from IPython.utils.traitlets import (
41 from IPython.utils.traitlets import (
42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
43 )
43 )
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Global utilities, errors and constants
46 # Global utilities, errors and constants
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49
49
50 class PrefilterError(Exception):
50 class PrefilterError(Exception):
51 pass
51 pass
52
52
53
53
54 # RegExp to identify potential function names
54 # RegExp to identify potential function names
55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
56
56
57 # RegExp to exclude strings with this start from autocalling. In
57 # RegExp to exclude strings with this start from autocalling. In
58 # particular, all binary operators should be excluded, so that if foo is
58 # particular, all binary operators should be excluded, so that if foo is
59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
60 # characters '!=()' don't need to be checked for, as the checkPythonChars
60 # characters '!=()' don't need to be checked for, as the checkPythonChars
61 # routine explicitely does so, to catch direct calls and rebindings of
61 # routine explicitely does so, to catch direct calls and rebindings of
62 # existing names.
62 # existing names.
63
63
64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
65 # it affects the rest of the group in square brackets.
65 # it affects the rest of the group in square brackets.
66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
67 r'|^is |^not |^in |^and |^or ')
67 r'|^is |^not |^in |^and |^or ')
68
68
69 # try to catch also methods for stuff in lists/tuples/dicts: off
69 # try to catch also methods for stuff in lists/tuples/dicts: off
70 # (experimental). For this to work, the line_split regexp would need
70 # (experimental). For this to work, the line_split regexp would need
71 # to be modified so it wouldn't break things at '['. That line is
71 # to be modified so it wouldn't break things at '['. That line is
72 # nasty enough that I shouldn't change it until I can test it _well_.
72 # nasty enough that I shouldn't change it until I can test it _well_.
73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
74
74
75
75
76 # Handler Check Utilities
76 # Handler Check Utilities
77 def is_shadowed(identifier, ip):
77 def is_shadowed(identifier, ip):
78 """Is the given identifier defined in one of the namespaces which shadow
78 """Is the given identifier defined in one of the namespaces which shadow
79 the alias and magic namespaces? Note that an identifier is different
79 the alias and magic namespaces? Note that an identifier is different
80 than ifun, because it can not contain a '.' character."""
80 than ifun, because it can not contain a '.' character."""
81 # This is much safer than calling ofind, which can change state
81 # This is much safer than calling ofind, which can change state
82 return (identifier in ip.user_ns \
82 return (identifier in ip.user_ns \
83 or identifier in ip.user_global_ns \
83 or identifier in ip.user_global_ns \
84 or identifier in ip.ns_table['builtin']\
84 or identifier in ip.ns_table['builtin']\
85 or iskeyword(identifier))
85 or iskeyword(identifier))
86
86
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Main Prefilter manager
89 # Main Prefilter manager
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92
92
93 class PrefilterManager(Configurable):
93 class PrefilterManager(Configurable):
94 """Main prefilter component.
94 """Main prefilter component.
95
95
96 The IPython prefilter is run on all user input before it is run. The
96 The IPython prefilter is run on all user input before it is run. The
97 prefilter consumes lines of input and produces transformed lines of
97 prefilter consumes lines of input and produces transformed lines of
98 input.
98 input.
99
99
100 The iplementation consists of two phases:
100 The iplementation consists of two phases:
101
101
102 1. Transformers
102 1. Transformers
103 2. Checkers and handlers
103 2. Checkers and handlers
104
104
105 Over time, we plan on deprecating the checkers and handlers and doing
105 Over time, we plan on deprecating the checkers and handlers and doing
106 everything in the transformers.
106 everything in the transformers.
107
107
108 The transformers are instances of :class:`PrefilterTransformer` and have
108 The transformers are instances of :class:`PrefilterTransformer` and have
109 a single method :meth:`transform` that takes a line and returns a
109 a single method :meth:`transform` that takes a line and returns a
110 transformed line. The transformation can be accomplished using any
110 transformed line. The transformation can be accomplished using any
111 tool, but our current ones use regular expressions for speed.
111 tool, but our current ones use regular expressions for speed.
112
112
113 After all the transformers have been run, the line is fed to the checkers,
113 After all the transformers have been run, the line is fed to the checkers,
114 which are instances of :class:`PrefilterChecker`. The line is passed to
114 which are instances of :class:`PrefilterChecker`. The line is passed to
115 the :meth:`check` method, which either returns `None` or a
115 the :meth:`check` method, which either returns `None` or a
116 :class:`PrefilterHandler` instance. If `None` is returned, the other
116 :class:`PrefilterHandler` instance. If `None` is returned, the other
117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
118 the line is passed to the :meth:`handle` method of the returned
118 the line is passed to the :meth:`handle` method of the returned
119 handler and no further checkers are tried.
119 handler and no further checkers are tried.
120
120
121 Both transformers and checkers have a `priority` attribute, that determines
121 Both transformers and checkers have a `priority` attribute, that determines
122 the order in which they are called. Smaller priorities are tried first.
122 the order in which they are called. Smaller priorities are tried first.
123
123
124 Both transformers and checkers also have `enabled` attribute, which is
124 Both transformers and checkers also have `enabled` attribute, which is
125 a boolean that determines if the instance is used.
125 a boolean that determines if the instance is used.
126
126
127 Users or developers can change the priority or enabled attribute of
127 Users or developers can change the priority or enabled attribute of
128 transformers or checkers, but they must call the :meth:`sort_checkers`
128 transformers or checkers, but they must call the :meth:`sort_checkers`
129 or :meth:`sort_transformers` method after changing the priority.
129 or :meth:`sort_transformers` method after changing the priority.
130 """
130 """
131
131
132 multi_line_specials = CBool(True, config=True)
132 multi_line_specials = CBool(True, config=True)
133 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
133 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
134
134
135 def __init__(self, shell=None, **kwargs):
135 def __init__(self, shell=None, **kwargs):
136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
137 self.shell = shell
137 self.shell = shell
138 self.init_transformers()
138 self.init_transformers()
139 self.init_handlers()
139 self.init_handlers()
140 self.init_checkers()
140 self.init_checkers()
141
141
142 #-------------------------------------------------------------------------
142 #-------------------------------------------------------------------------
143 # API for managing transformers
143 # API for managing transformers
144 #-------------------------------------------------------------------------
144 #-------------------------------------------------------------------------
145
145
146 def init_transformers(self):
146 def init_transformers(self):
147 """Create the default transformers."""
147 """Create the default transformers."""
148 self._transformers = []
148 self._transformers = []
149 for transformer_cls in _default_transformers:
149 for transformer_cls in _default_transformers:
150 transformer_cls(
150 transformer_cls(
151 shell=self.shell, prefilter_manager=self, parent=self
151 shell=self.shell, prefilter_manager=self, parent=self
152 )
152 )
153
153
154 def sort_transformers(self):
154 def sort_transformers(self):
155 """Sort the transformers by priority.
155 """Sort the transformers by priority.
156
156
157 This must be called after the priority of a transformer is changed.
157 This must be called after the priority of a transformer is changed.
158 The :meth:`register_transformer` method calls this automatically.
158 The :meth:`register_transformer` method calls this automatically.
159 """
159 """
160 self._transformers.sort(key=lambda x: x.priority)
160 self._transformers.sort(key=lambda x: x.priority)
161
161
162 @property
162 @property
163 def transformers(self):
163 def transformers(self):
164 """Return a list of checkers, sorted by priority."""
164 """Return a list of checkers, sorted by priority."""
165 return self._transformers
165 return self._transformers
166
166
167 def register_transformer(self, transformer):
167 def register_transformer(self, transformer):
168 """Register a transformer instance."""
168 """Register a transformer instance."""
169 if transformer not in self._transformers:
169 if transformer not in self._transformers:
170 self._transformers.append(transformer)
170 self._transformers.append(transformer)
171 self.sort_transformers()
171 self.sort_transformers()
172
172
173 def unregister_transformer(self, transformer):
173 def unregister_transformer(self, transformer):
174 """Unregister a transformer instance."""
174 """Unregister a transformer instance."""
175 if transformer in self._transformers:
175 if transformer in self._transformers:
176 self._transformers.remove(transformer)
176 self._transformers.remove(transformer)
177
177
178 #-------------------------------------------------------------------------
178 #-------------------------------------------------------------------------
179 # API for managing checkers
179 # API for managing checkers
180 #-------------------------------------------------------------------------
180 #-------------------------------------------------------------------------
181
181
182 def init_checkers(self):
182 def init_checkers(self):
183 """Create the default checkers."""
183 """Create the default checkers."""
184 self._checkers = []
184 self._checkers = []
185 for checker in _default_checkers:
185 for checker in _default_checkers:
186 checker(
186 checker(
187 shell=self.shell, prefilter_manager=self, parent=self
187 shell=self.shell, prefilter_manager=self, parent=self
188 )
188 )
189
189
190 def sort_checkers(self):
190 def sort_checkers(self):
191 """Sort the checkers by priority.
191 """Sort the checkers by priority.
192
192
193 This must be called after the priority of a checker is changed.
193 This must be called after the priority of a checker is changed.
194 The :meth:`register_checker` method calls this automatically.
194 The :meth:`register_checker` method calls this automatically.
195 """
195 """
196 self._checkers.sort(key=lambda x: x.priority)
196 self._checkers.sort(key=lambda x: x.priority)
197
197
198 @property
198 @property
199 def checkers(self):
199 def checkers(self):
200 """Return a list of checkers, sorted by priority."""
200 """Return a list of checkers, sorted by priority."""
201 return self._checkers
201 return self._checkers
202
202
203 def register_checker(self, checker):
203 def register_checker(self, checker):
204 """Register a checker instance."""
204 """Register a checker instance."""
205 if checker not in self._checkers:
205 if checker not in self._checkers:
206 self._checkers.append(checker)
206 self._checkers.append(checker)
207 self.sort_checkers()
207 self.sort_checkers()
208
208
209 def unregister_checker(self, checker):
209 def unregister_checker(self, checker):
210 """Unregister a checker instance."""
210 """Unregister a checker instance."""
211 if checker in self._checkers:
211 if checker in self._checkers:
212 self._checkers.remove(checker)
212 self._checkers.remove(checker)
213
213
214 #-------------------------------------------------------------------------
214 #-------------------------------------------------------------------------
215 # API for managing handlers
215 # API for managing handlers
216 #-------------------------------------------------------------------------
216 #-------------------------------------------------------------------------
217
217
218 def init_handlers(self):
218 def init_handlers(self):
219 """Create the default handlers."""
219 """Create the default handlers."""
220 self._handlers = {}
220 self._handlers = {}
221 self._esc_handlers = {}
221 self._esc_handlers = {}
222 for handler in _default_handlers:
222 for handler in _default_handlers:
223 handler(
223 handler(
224 shell=self.shell, prefilter_manager=self, parent=self
224 shell=self.shell, prefilter_manager=self, parent=self
225 )
225 )
226
226
227 @property
227 @property
228 def handlers(self):
228 def handlers(self):
229 """Return a dict of all the handlers."""
229 """Return a dict of all the handlers."""
230 return self._handlers
230 return self._handlers
231
231
232 def register_handler(self, name, handler, esc_strings):
232 def register_handler(self, name, handler, esc_strings):
233 """Register a handler instance by name with esc_strings."""
233 """Register a handler instance by name with esc_strings."""
234 self._handlers[name] = handler
234 self._handlers[name] = handler
235 for esc_str in esc_strings:
235 for esc_str in esc_strings:
236 self._esc_handlers[esc_str] = handler
236 self._esc_handlers[esc_str] = handler
237
237
238 def unregister_handler(self, name, handler, esc_strings):
238 def unregister_handler(self, name, handler, esc_strings):
239 """Unregister a handler instance by name with esc_strings."""
239 """Unregister a handler instance by name with esc_strings."""
240 try:
240 try:
241 del self._handlers[name]
241 del self._handlers[name]
242 except KeyError:
242 except KeyError:
243 pass
243 pass
244 for esc_str in esc_strings:
244 for esc_str in esc_strings:
245 h = self._esc_handlers.get(esc_str)
245 h = self._esc_handlers.get(esc_str)
246 if h is handler:
246 if h is handler:
247 del self._esc_handlers[esc_str]
247 del self._esc_handlers[esc_str]
248
248
249 def get_handler_by_name(self, name):
249 def get_handler_by_name(self, name):
250 """Get a handler by its name."""
250 """Get a handler by its name."""
251 return self._handlers.get(name)
251 return self._handlers.get(name)
252
252
253 def get_handler_by_esc(self, esc_str):
253 def get_handler_by_esc(self, esc_str):
254 """Get a handler by its escape string."""
254 """Get a handler by its escape string."""
255 return self._esc_handlers.get(esc_str)
255 return self._esc_handlers.get(esc_str)
256
256
257 #-------------------------------------------------------------------------
257 #-------------------------------------------------------------------------
258 # Main prefiltering API
258 # Main prefiltering API
259 #-------------------------------------------------------------------------
259 #-------------------------------------------------------------------------
260
260
261 def prefilter_line_info(self, line_info):
261 def prefilter_line_info(self, line_info):
262 """Prefilter a line that has been converted to a LineInfo object.
262 """Prefilter a line that has been converted to a LineInfo object.
263
263
264 This implements the checker/handler part of the prefilter pipe.
264 This implements the checker/handler part of the prefilter pipe.
265 """
265 """
266 # print "prefilter_line_info: ", line_info
266 # print "prefilter_line_info: ", line_info
267 handler = self.find_handler(line_info)
267 handler = self.find_handler(line_info)
268 return handler.handle(line_info)
268 return handler.handle(line_info)
269
269
270 def find_handler(self, line_info):
270 def find_handler(self, line_info):
271 """Find a handler for the line_info by trying checkers."""
271 """Find a handler for the line_info by trying checkers."""
272 for checker in self.checkers:
272 for checker in self.checkers:
273 if checker.enabled:
273 if checker.enabled:
274 handler = checker.check(line_info)
274 handler = checker.check(line_info)
275 if handler:
275 if handler:
276 return handler
276 return handler
277 return self.get_handler_by_name('normal')
277 return self.get_handler_by_name('normal')
278
278
279 def transform_line(self, line, continue_prompt):
279 def transform_line(self, line, continue_prompt):
280 """Calls the enabled transformers in order of increasing priority."""
280 """Calls the enabled transformers in order of increasing priority."""
281 for transformer in self.transformers:
281 for transformer in self.transformers:
282 if transformer.enabled:
282 if transformer.enabled:
283 line = transformer.transform(line, continue_prompt)
283 line = transformer.transform(line, continue_prompt)
284 return line
284 return line
285
285
286 def prefilter_line(self, line, continue_prompt=False):
286 def prefilter_line(self, line, continue_prompt=False):
287 """Prefilter a single input line as text.
287 """Prefilter a single input line as text.
288
288
289 This method prefilters a single line of text by calling the
289 This method prefilters a single line of text by calling the
290 transformers and then the checkers/handlers.
290 transformers and then the checkers/handlers.
291 """
291 """
292
292
293 # print "prefilter_line: ", line, continue_prompt
293 # print "prefilter_line: ", line, continue_prompt
294 # All handlers *must* return a value, even if it's blank ('').
294 # All handlers *must* return a value, even if it's blank ('').
295
295
296 # save the line away in case we crash, so the post-mortem handler can
296 # save the line away in case we crash, so the post-mortem handler can
297 # record it
297 # record it
298 self.shell._last_input_line = line
298 self.shell._last_input_line = line
299
299
300 if not line:
300 if not line:
301 # Return immediately on purely empty lines, so that if the user
301 # Return immediately on purely empty lines, so that if the user
302 # previously typed some whitespace that started a continuation
302 # previously typed some whitespace that started a continuation
303 # prompt, he can break out of that loop with just an empty line.
303 # prompt, he can break out of that loop with just an empty line.
304 # This is how the default python prompt works.
304 # This is how the default python prompt works.
305 return ''
305 return ''
306
306
307 # At this point, we invoke our transformers.
307 # At this point, we invoke our transformers.
308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
309 line = self.transform_line(line, continue_prompt)
309 line = self.transform_line(line, continue_prompt)
310
310
311 # Now we compute line_info for the checkers and handlers
311 # Now we compute line_info for the checkers and handlers
312 line_info = LineInfo(line, continue_prompt)
312 line_info = LineInfo(line, continue_prompt)
313
313
314 # the input history needs to track even empty lines
314 # the input history needs to track even empty lines
315 stripped = line.strip()
315 stripped = line.strip()
316
316
317 normal_handler = self.get_handler_by_name('normal')
317 normal_handler = self.get_handler_by_name('normal')
318 if not stripped:
318 if not stripped:
319 return normal_handler.handle(line_info)
319 return normal_handler.handle(line_info)
320
320
321 # special handlers are only allowed for single line statements
321 # special handlers are only allowed for single line statements
322 if continue_prompt and not self.multi_line_specials:
322 if continue_prompt and not self.multi_line_specials:
323 return normal_handler.handle(line_info)
323 return normal_handler.handle(line_info)
324
324
325 prefiltered = self.prefilter_line_info(line_info)
325 prefiltered = self.prefilter_line_info(line_info)
326 # print "prefiltered line: %r" % prefiltered
326 # print "prefiltered line: %r" % prefiltered
327 return prefiltered
327 return prefiltered
328
328
329 def prefilter_lines(self, lines, continue_prompt=False):
329 def prefilter_lines(self, lines, continue_prompt=False):
330 """Prefilter multiple input lines of text.
330 """Prefilter multiple input lines of text.
331
331
332 This is the main entry point for prefiltering multiple lines of
332 This is the main entry point for prefiltering multiple lines of
333 input. This simply calls :meth:`prefilter_line` for each line of
333 input. This simply calls :meth:`prefilter_line` for each line of
334 input.
334 input.
335
335
336 This covers cases where there are multiple lines in the user entry,
336 This covers cases where there are multiple lines in the user entry,
337 which is the case when the user goes back to a multiline history
337 which is the case when the user goes back to a multiline history
338 entry and presses enter.
338 entry and presses enter.
339 """
339 """
340 llines = lines.rstrip('\n').split('\n')
340 llines = lines.rstrip('\n').split('\n')
341 # We can get multiple lines in one shot, where multiline input 'blends'
341 # We can get multiple lines in one shot, where multiline input 'blends'
342 # into one line, in cases like recalling from the readline history
342 # into one line, in cases like recalling from the readline history
343 # buffer. We need to make sure that in such cases, we correctly
343 # buffer. We need to make sure that in such cases, we correctly
344 # communicate downstream which line is first and which are continuation
344 # communicate downstream which line is first and which are continuation
345 # ones.
345 # ones.
346 if len(llines) > 1:
346 if len(llines) > 1:
347 out = '\n'.join([self.prefilter_line(line, lnum>0)
347 out = '\n'.join([self.prefilter_line(line, lnum>0)
348 for lnum, line in enumerate(llines) ])
348 for lnum, line in enumerate(llines) ])
349 else:
349 else:
350 out = self.prefilter_line(llines[0], continue_prompt)
350 out = self.prefilter_line(llines[0], continue_prompt)
351
351
352 return out
352 return out
353
353
354 #-----------------------------------------------------------------------------
354 #-----------------------------------------------------------------------------
355 # Prefilter transformers
355 # Prefilter transformers
356 #-----------------------------------------------------------------------------
356 #-----------------------------------------------------------------------------
357
357
358
358
359 class PrefilterTransformer(Configurable):
359 class PrefilterTransformer(Configurable):
360 """Transform a line of user input."""
360 """Transform a line of user input."""
361
361
362 priority = Integer(100, config=True)
362 priority = Integer(100, config=True)
363 # Transformers don't currently use shell or prefilter_manager, but as we
363 # Transformers don't currently use shell or prefilter_manager, but as we
364 # move away from checkers and handlers, they will need them.
364 # move away from checkers and handlers, they will need them.
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
367 enabled = Bool(True, config=True)
367 enabled = Bool(True, config=True)
368
368
369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
370 super(PrefilterTransformer, self).__init__(
370 super(PrefilterTransformer, self).__init__(
371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
372 )
372 )
373 self.prefilter_manager.register_transformer(self)
373 self.prefilter_manager.register_transformer(self)
374
374
375 def transform(self, line, continue_prompt):
375 def transform(self, line, continue_prompt):
376 """Transform a line, returning the new one."""
376 """Transform a line, returning the new one."""
377 return None
377 return None
378
378
379 def __repr__(self):
379 def __repr__(self):
380 return "<%s(priority=%r, enabled=%r)>" % (
380 return "<%s(priority=%r, enabled=%r)>" % (
381 self.__class__.__name__, self.priority, self.enabled)
381 self.__class__.__name__, self.priority, self.enabled)
382
382
383
383
384 #-----------------------------------------------------------------------------
384 #-----------------------------------------------------------------------------
385 # Prefilter checkers
385 # Prefilter checkers
386 #-----------------------------------------------------------------------------
386 #-----------------------------------------------------------------------------
387
387
388
388
389 class PrefilterChecker(Configurable):
389 class PrefilterChecker(Configurable):
390 """Inspect an input line and return a handler for that line."""
390 """Inspect an input line and return a handler for that line."""
391
391
392 priority = Integer(100, config=True)
392 priority = Integer(100, config=True)
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
395 enabled = Bool(True, config=True)
395 enabled = Bool(True, config=True)
396
396
397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
398 super(PrefilterChecker, self).__init__(
398 super(PrefilterChecker, self).__init__(
399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
400 )
400 )
401 self.prefilter_manager.register_checker(self)
401 self.prefilter_manager.register_checker(self)
402
402
403 def check(self, line_info):
403 def check(self, line_info):
404 """Inspect line_info and return a handler instance or None."""
404 """Inspect line_info and return a handler instance or None."""
405 return None
405 return None
406
406
407 def __repr__(self):
407 def __repr__(self):
408 return "<%s(priority=%r, enabled=%r)>" % (
408 return "<%s(priority=%r, enabled=%r)>" % (
409 self.__class__.__name__, self.priority, self.enabled)
409 self.__class__.__name__, self.priority, self.enabled)
410
410
411
411
412 class EmacsChecker(PrefilterChecker):
412 class EmacsChecker(PrefilterChecker):
413
413
414 priority = Integer(100, config=True)
414 priority = Integer(100, config=True)
415 enabled = Bool(False, config=True)
415 enabled = Bool(False, config=True)
416
416
417 def check(self, line_info):
417 def check(self, line_info):
418 "Emacs ipython-mode tags certain input lines."
418 "Emacs ipython-mode tags certain input lines."
419 if line_info.line.endswith('# PYTHON-MODE'):
419 if line_info.line.endswith('# PYTHON-MODE'):
420 return self.prefilter_manager.get_handler_by_name('emacs')
420 return self.prefilter_manager.get_handler_by_name('emacs')
421 else:
421 else:
422 return None
422 return None
423
423
424
424
425 class MacroChecker(PrefilterChecker):
425 class MacroChecker(PrefilterChecker):
426
426
427 priority = Integer(250, config=True)
427 priority = Integer(250, config=True)
428
428
429 def check(self, line_info):
429 def check(self, line_info):
430 obj = self.shell.user_ns.get(line_info.ifun)
430 obj = self.shell.user_ns.get(line_info.ifun)
431 if isinstance(obj, Macro):
431 if isinstance(obj, Macro):
432 return self.prefilter_manager.get_handler_by_name('macro')
432 return self.prefilter_manager.get_handler_by_name('macro')
433 else:
433 else:
434 return None
434 return None
435
435
436
436
437 class IPyAutocallChecker(PrefilterChecker):
437 class IPyAutocallChecker(PrefilterChecker):
438
438
439 priority = Integer(300, config=True)
439 priority = Integer(300, config=True)
440
440
441 def check(self, line_info):
441 def check(self, line_info):
442 "Instances of IPyAutocall in user_ns get autocalled immediately"
442 "Instances of IPyAutocall in user_ns get autocalled immediately"
443 obj = self.shell.user_ns.get(line_info.ifun, None)
443 obj = self.shell.user_ns.get(line_info.ifun, None)
444 if isinstance(obj, IPyAutocall):
444 if isinstance(obj, IPyAutocall):
445 obj.set_ip(self.shell)
445 obj.set_ip(self.shell)
446 return self.prefilter_manager.get_handler_by_name('auto')
446 return self.prefilter_manager.get_handler_by_name('auto')
447 else:
447 else:
448 return None
448 return None
449
449
450
450
451 class AssignmentChecker(PrefilterChecker):
451 class AssignmentChecker(PrefilterChecker):
452
452
453 priority = Integer(600, config=True)
453 priority = Integer(600, config=True)
454
454
455 def check(self, line_info):
455 def check(self, line_info):
456 """Check to see if user is assigning to a var for the first time, in
456 """Check to see if user is assigning to a var for the first time, in
457 which case we want to avoid any sort of automagic / autocall games.
457 which case we want to avoid any sort of automagic / autocall games.
458
458
459 This allows users to assign to either alias or magic names true python
459 This allows users to assign to either alias or magic names true python
460 variables (the magic/alias systems always take second seat to true
460 variables (the magic/alias systems always take second seat to true
461 python code). E.g. ls='hi', or ls,that=1,2"""
461 python code). E.g. ls='hi', or ls,that=1,2"""
462 if line_info.the_rest:
462 if line_info.the_rest:
463 if line_info.the_rest[0] in '=,':
463 if line_info.the_rest[0] in '=,':
464 return self.prefilter_manager.get_handler_by_name('normal')
464 return self.prefilter_manager.get_handler_by_name('normal')
465 else:
465 else:
466 return None
466 return None
467
467
468
468
469 class AutoMagicChecker(PrefilterChecker):
469 class AutoMagicChecker(PrefilterChecker):
470
470
471 priority = Integer(700, config=True)
471 priority = Integer(700, config=True)
472
472
473 def check(self, line_info):
473 def check(self, line_info):
474 """If the ifun is magic, and automagic is on, run it. Note: normal,
474 """If the ifun is magic, and automagic is on, run it. Note: normal,
475 non-auto magic would already have been triggered via '%' in
475 non-auto magic would already have been triggered via '%' in
476 check_esc_chars. This just checks for automagic. Also, before
476 check_esc_chars. This just checks for automagic. Also, before
477 triggering the magic handler, make sure that there is nothing in the
477 triggering the magic handler, make sure that there is nothing in the
478 user namespace which could shadow it."""
478 user namespace which could shadow it."""
479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
480 return None
480 return None
481
481
482 # We have a likely magic method. Make sure we should actually call it.
482 # We have a likely magic method. Make sure we should actually call it.
483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
484 return None
484 return None
485
485
486 head = line_info.ifun.split('.',1)[0]
486 head = line_info.ifun.split('.',1)[0]
487 if is_shadowed(head, self.shell):
487 if is_shadowed(head, self.shell):
488 return None
488 return None
489
489
490 return self.prefilter_manager.get_handler_by_name('magic')
490 return self.prefilter_manager.get_handler_by_name('magic')
491
491
492
492
493 class PythonOpsChecker(PrefilterChecker):
493 class PythonOpsChecker(PrefilterChecker):
494
494
495 priority = Integer(900, config=True)
495 priority = Integer(900, config=True)
496
496
497 def check(self, line_info):
497 def check(self, line_info):
498 """If the 'rest' of the line begins with a function call or pretty much
498 """If the 'rest' of the line begins with a function call or pretty much
499 any python operator, we should simply execute the line (regardless of
499 any python operator, we should simply execute the line (regardless of
500 whether or not there's a possible autocall expansion). This avoids
500 whether or not there's a possible autocall expansion). This avoids
501 spurious (and very confusing) geattr() accesses."""
501 spurious (and very confusing) geattr() accesses."""
502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
503 return self.prefilter_manager.get_handler_by_name('normal')
503 return self.prefilter_manager.get_handler_by_name('normal')
504 else:
504 else:
505 return None
505 return None
506
506
507
507
508 class AutocallChecker(PrefilterChecker):
508 class AutocallChecker(PrefilterChecker):
509
509
510 priority = Integer(1000, config=True)
510 priority = Integer(1000, config=True)
511
511
512 function_name_regexp = CRegExp(re_fun_name, config=True,
512 function_name_regexp = CRegExp(re_fun_name, config=True,
513 help="RegExp to identify potential function names.")
513 help="RegExp to identify potential function names.")
514 exclude_regexp = CRegExp(re_exclude_auto, config=True,
514 exclude_regexp = CRegExp(re_exclude_auto, config=True,
515 help="RegExp to exclude strings with this start from autocalling.")
515 help="RegExp to exclude strings with this start from autocalling.")
516
516
517 def check(self, line_info):
517 def check(self, line_info):
518 "Check if the initial word/function is callable and autocall is on."
518 "Check if the initial word/function is callable and autocall is on."
519 if not self.shell.autocall:
519 if not self.shell.autocall:
520 return None
520 return None
521
521
522 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
522 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
523 if not oinfo['found']:
523 if not oinfo['found']:
524 return None
524 return None
525
525
526 if callable(oinfo['obj']) \
526 if callable(oinfo['obj']) \
527 and (not self.exclude_regexp.match(line_info.the_rest)) \
527 and (not self.exclude_regexp.match(line_info.the_rest)) \
528 and self.function_name_regexp.match(line_info.ifun):
528 and self.function_name_regexp.match(line_info.ifun):
529 return self.prefilter_manager.get_handler_by_name('auto')
529 return self.prefilter_manager.get_handler_by_name('auto')
530 else:
530 else:
531 return None
531 return None
532
532
533
533
534 #-----------------------------------------------------------------------------
534 #-----------------------------------------------------------------------------
535 # Prefilter handlers
535 # Prefilter handlers
536 #-----------------------------------------------------------------------------
536 #-----------------------------------------------------------------------------
537
537
538
538
539 class PrefilterHandler(Configurable):
539 class PrefilterHandler(Configurable):
540
540
541 handler_name = Unicode('normal')
541 handler_name = Unicode('normal')
542 esc_strings = List([])
542 esc_strings = List([])
543 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
543 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
544 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
544 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
545
545
546 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
546 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
547 super(PrefilterHandler, self).__init__(
547 super(PrefilterHandler, self).__init__(
548 shell=shell, prefilter_manager=prefilter_manager, **kwargs
548 shell=shell, prefilter_manager=prefilter_manager, **kwargs
549 )
549 )
550 self.prefilter_manager.register_handler(
550 self.prefilter_manager.register_handler(
551 self.handler_name,
551 self.handler_name,
552 self,
552 self,
553 self.esc_strings
553 self.esc_strings
554 )
554 )
555
555
556 def handle(self, line_info):
556 def handle(self, line_info):
557 # print "normal: ", line_info
557 # print "normal: ", line_info
558 """Handle normal input lines. Use as a template for handlers."""
558 """Handle normal input lines. Use as a template for handlers."""
559
559
560 # With autoindent on, we need some way to exit the input loop, and I
560 # With autoindent on, we need some way to exit the input loop, and I
561 # don't want to force the user to have to backspace all the way to
561 # don't want to force the user to have to backspace all the way to
562 # clear the line. The rule will be in this case, that either two
562 # clear the line. The rule will be in this case, that either two
563 # lines of pure whitespace in a row, or a line of pure whitespace but
563 # lines of pure whitespace in a row, or a line of pure whitespace but
564 # of a size different to the indent level, will exit the input loop.
564 # of a size different to the indent level, will exit the input loop.
565 line = line_info.line
565 line = line_info.line
566 continue_prompt = line_info.continue_prompt
566 continue_prompt = line_info.continue_prompt
567
567
568 if (continue_prompt and
568 if (continue_prompt and
569 self.shell.autoindent and
569 self.shell.autoindent and
570 line.isspace() and
570 line.isspace() and
571 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
571 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
572 line = ''
572 line = ''
573
573
574 return line
574 return line
575
575
576 def __str__(self):
576 def __str__(self):
577 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
577 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
578
578
579
579
580 class MacroHandler(PrefilterHandler):
580 class MacroHandler(PrefilterHandler):
581 handler_name = Unicode("macro")
581 handler_name = Unicode("macro")
582
582
583 def handle(self, line_info):
583 def handle(self, line_info):
584 obj = self.shell.user_ns.get(line_info.ifun)
584 obj = self.shell.user_ns.get(line_info.ifun)
585 pre_space = line_info.pre_whitespace
585 pre_space = line_info.pre_whitespace
586 line_sep = "\n" + pre_space
586 line_sep = "\n" + pre_space
587 return pre_space + line_sep.join(obj.value.splitlines())
587 return pre_space + line_sep.join(obj.value.splitlines())
588
588
589
589
590 class MagicHandler(PrefilterHandler):
590 class MagicHandler(PrefilterHandler):
591
591
592 handler_name = Unicode('magic')
592 handler_name = Unicode('magic')
593 esc_strings = List([ESC_MAGIC])
593 esc_strings = List([ESC_MAGIC])
594
594
595 def handle(self, line_info):
595 def handle(self, line_info):
596 """Execute magic functions."""
596 """Execute magic functions."""
597 ifun = line_info.ifun
597 ifun = line_info.ifun
598 the_rest = line_info.the_rest
598 the_rest = line_info.the_rest
599 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
599 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
600 (ifun + " " + the_rest))
600 (ifun + " " + the_rest))
601 return cmd
601 return cmd
602
602
603
603
604 class AutoHandler(PrefilterHandler):
604 class AutoHandler(PrefilterHandler):
605
605
606 handler_name = Unicode('auto')
606 handler_name = Unicode('auto')
607 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
607 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
608
608
609 def handle(self, line_info):
609 def handle(self, line_info):
610 """Handle lines which can be auto-executed, quoting if requested."""
610 """Handle lines which can be auto-executed, quoting if requested."""
611 line = line_info.line
611 line = line_info.line
612 ifun = line_info.ifun
612 ifun = line_info.ifun
613 the_rest = line_info.the_rest
613 the_rest = line_info.the_rest
614 pre = line_info.pre
614 pre = line_info.pre
615 esc = line_info.esc
615 esc = line_info.esc
616 continue_prompt = line_info.continue_prompt
616 continue_prompt = line_info.continue_prompt
617 obj = line_info.ofind(self.shell)['obj']
617 obj = line_info.ofind(self.shell)['obj']
618 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun,the_rest) # dbg
618 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun,the_rest) # dbg
619
619
620 # This should only be active for single-line input!
620 # This should only be active for single-line input!
621 if continue_prompt:
621 if continue_prompt:
622 return line
622 return line
623
623
624 force_auto = isinstance(obj, IPyAutocall)
624 force_auto = isinstance(obj, IPyAutocall)
625
625
626 # User objects sometimes raise exceptions on attribute access other
626 # User objects sometimes raise exceptions on attribute access other
627 # than AttributeError (we've seen it in the past), so it's safest to be
627 # than AttributeError (we've seen it in the past), so it's safest to be
628 # ultra-conservative here and catch all.
628 # ultra-conservative here and catch all.
629 try:
629 try:
630 auto_rewrite = obj.rewrite
630 auto_rewrite = obj.rewrite
631 except Exception:
631 except Exception:
632 auto_rewrite = True
632 auto_rewrite = True
633
633
634 if esc == ESC_QUOTE:
634 if esc == ESC_QUOTE:
635 # Auto-quote splitting on whitespace
635 # Auto-quote splitting on whitespace
636 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
636 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
637 elif esc == ESC_QUOTE2:
637 elif esc == ESC_QUOTE2:
638 # Auto-quote whole string
638 # Auto-quote whole string
639 newcmd = '%s("%s")' % (ifun,the_rest)
639 newcmd = '%s("%s")' % (ifun,the_rest)
640 elif esc == ESC_PAREN:
640 elif esc == ESC_PAREN:
641 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
641 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
642 else:
642 else:
643 # Auto-paren.
643 # Auto-paren.
644 if force_auto:
644 if force_auto:
645 # Don't rewrite if it is already a call.
645 # Don't rewrite if it is already a call.
646 do_rewrite = not the_rest.startswith('(')
646 do_rewrite = not the_rest.startswith('(')
647 else:
647 else:
648 if not the_rest:
648 if not the_rest:
649 # We only apply it to argument-less calls if the autocall
649 # We only apply it to argument-less calls if the autocall
650 # parameter is set to 2.
650 # parameter is set to 2.
651 do_rewrite = (self.shell.autocall >= 2)
651 do_rewrite = (self.shell.autocall >= 2)
652 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
652 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
653 # Don't autocall in this case: item access for an object
653 # Don't autocall in this case: item access for an object
654 # which is BOTH callable and implements __getitem__.
654 # which is BOTH callable and implements __getitem__.
655 do_rewrite = False
655 do_rewrite = False
656 else:
656 else:
657 do_rewrite = True
657 do_rewrite = True
658
658
659 # Figure out the rewritten command
659 # Figure out the rewritten command
660 if do_rewrite:
660 if do_rewrite:
661 if the_rest.endswith(';'):
661 if the_rest.endswith(';'):
662 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
662 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
663 else:
663 else:
664 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
664 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
665 else:
665 else:
666 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
666 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
667 return normal_handler.handle(line_info)
667 return normal_handler.handle(line_info)
668
668
669 # Display the rewritten call
669 # Display the rewritten call
670 if auto_rewrite:
670 if auto_rewrite:
671 self.shell.auto_rewrite_input(newcmd)
671 self.shell.auto_rewrite_input(newcmd)
672
672
673 return newcmd
673 return newcmd
674
674
675
675
676 class EmacsHandler(PrefilterHandler):
676 class EmacsHandler(PrefilterHandler):
677
677
678 handler_name = Unicode('emacs')
678 handler_name = Unicode('emacs')
679 esc_strings = List([])
679 esc_strings = List([])
680
680
681 def handle(self, line_info):
681 def handle(self, line_info):
682 """Handle input lines marked by python-mode."""
682 """Handle input lines marked by python-mode."""
683
683
684 # Currently, nothing is done. Later more functionality can be added
684 # Currently, nothing is done. Later more functionality can be added
685 # here if needed.
685 # here if needed.
686
686
687 # The input cache shouldn't be updated
687 # The input cache shouldn't be updated
688 return line_info.line
688 return line_info.line
689
689
690
690
691 #-----------------------------------------------------------------------------
691 #-----------------------------------------------------------------------------
692 # Defaults
692 # Defaults
693 #-----------------------------------------------------------------------------
693 #-----------------------------------------------------------------------------
694
694
695
695
696 _default_transformers = [
696 _default_transformers = [
697 ]
697 ]
698
698
699 _default_checkers = [
699 _default_checkers = [
700 EmacsChecker,
700 EmacsChecker,
701 MacroChecker,
701 MacroChecker,
702 IPyAutocallChecker,
702 IPyAutocallChecker,
703 AssignmentChecker,
703 AssignmentChecker,
704 AutoMagicChecker,
704 AutoMagicChecker,
705 PythonOpsChecker,
705 PythonOpsChecker,
706 AutocallChecker
706 AutocallChecker
707 ]
707 ]
708
708
709 _default_handlers = [
709 _default_handlers = [
710 PrefilterHandler,
710 PrefilterHandler,
711 MacroHandler,
711 MacroHandler,
712 MagicHandler,
712 MagicHandler,
713 AutoHandler,
713 AutoHandler,
714 EmacsHandler
714 EmacsHandler
715 ]
715 ]
@@ -1,442 +1,442 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 * Thomas Kluyver
8 * Thomas Kluyver
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
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 import os
23 import os
24 import re
24 import re
25 import socket
25 import socket
26 import sys
26 import sys
27 import time
27 import time
28
28
29 from string import Formatter
29 from string import Formatter
30
30
31 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
32 from IPython.core import release
32 from IPython.core import release
33 from IPython.utils import coloransi, py3compat
33 from IPython.utils import coloransi, py3compat
34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Color schemes for prompts
37 # Color schemes for prompts
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 InputColors = coloransi.InputTermColors # just a shorthand
40 InputColors = coloransi.InputTermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
41 Colors = coloransi.TermColors # just a shorthand
42
42
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44
44
45 PColNoColors = coloransi.ColorScheme(
45 PColNoColors = coloransi.ColorScheme(
46 'NoColor',
46 'NoColor',
47 in_prompt = InputColors.NoColor, # Input prompt
47 in_prompt = InputColors.NoColor, # Input prompt
48 in_number = InputColors.NoColor, # Input prompt number
48 in_number = InputColors.NoColor, # Input prompt number
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51
51
52 out_prompt = Colors.NoColor, # Output prompt
52 out_prompt = Colors.NoColor, # Output prompt
53 out_number = Colors.NoColor, # Output prompt number
53 out_number = Colors.NoColor, # Output prompt number
54
54
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 )
56 )
57
57
58 # make some schemes as instances so we can copy them for modification easily:
58 # make some schemes as instances so we can copy them for modification easily:
59 PColLinux = coloransi.ColorScheme(
59 PColLinux = coloransi.ColorScheme(
60 'Linux',
60 'Linux',
61 in_prompt = InputColors.Green,
61 in_prompt = InputColors.Green,
62 in_number = InputColors.LightGreen,
62 in_number = InputColors.LightGreen,
63 in_prompt2 = InputColors.Green,
63 in_prompt2 = InputColors.Green,
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65
65
66 out_prompt = Colors.Red,
66 out_prompt = Colors.Red,
67 out_number = Colors.LightRed,
67 out_number = Colors.LightRed,
68
68
69 normal = Colors.Normal
69 normal = Colors.Normal
70 )
70 )
71
71
72 # Slightly modified Linux for light backgrounds
72 # Slightly modified Linux for light backgrounds
73 PColLightBG = PColLinux.copy('LightBG')
73 PColLightBG = PColLinux.copy('LightBG')
74
74
75 PColLightBG.colors.update(
75 PColLightBG.colors.update(
76 in_prompt = InputColors.Blue,
76 in_prompt = InputColors.Blue,
77 in_number = InputColors.LightBlue,
77 in_number = InputColors.LightBlue,
78 in_prompt2 = InputColors.Blue
78 in_prompt2 = InputColors.Blue
79 )
79 )
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Utilities
82 # Utilities
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 class LazyEvaluate(object):
85 class LazyEvaluate(object):
86 """This is used for formatting strings with values that need to be updated
86 """This is used for formatting strings with values that need to be updated
87 at that time, such as the current time or working directory."""
87 at that time, such as the current time or working directory."""
88 def __init__(self, func, *args, **kwargs):
88 def __init__(self, func, *args, **kwargs):
89 self.func = func
89 self.func = func
90 self.args = args
90 self.args = args
91 self.kwargs = kwargs
91 self.kwargs = kwargs
92
92
93 def __call__(self, **kwargs):
93 def __call__(self, **kwargs):
94 self.kwargs.update(kwargs)
94 self.kwargs.update(kwargs)
95 return self.func(*self.args, **self.kwargs)
95 return self.func(*self.args, **self.kwargs)
96
96
97 def __str__(self):
97 def __str__(self):
98 return str(self())
98 return str(self())
99
99
100 def __unicode__(self):
100 def __unicode__(self):
101 return py3compat.unicode_type(self())
101 return py3compat.unicode_type(self())
102
102
103 def __format__(self, format_spec):
103 def __format__(self, format_spec):
104 return format(self(), format_spec)
104 return format(self(), format_spec)
105
105
106 def multiple_replace(dict, text):
106 def multiple_replace(dict, text):
107 """ Replace in 'text' all occurences of any key in the given
107 """ Replace in 'text' all occurences of any key in the given
108 dictionary by its corresponding value. Returns the new string."""
108 dictionary by its corresponding value. Returns the new string."""
109
109
110 # Function by Xavier Defrang, originally found at:
110 # Function by Xavier Defrang, originally found at:
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112
112
113 # Create a regular expression from the dictionary keys
113 # Create a regular expression from the dictionary keys
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 # For each match, look-up corresponding value in dictionary
115 # For each match, look-up corresponding value in dictionary
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117
117
118 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
119 # Special characters that can be used in prompt templates, mainly bash-like
119 # Special characters that can be used in prompt templates, mainly bash-like
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121
121
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
123 # never be expanded out into '~'. Basically anything which can never be a
123 # never be expanded out into '~'. Basically anything which can never be a
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
124 # reasonable directory name will do, we just want the $HOME -> '~' operation
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
125 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 # prompt call.
126 # prompt call.
127
127
128 # FIXME:
128 # FIXME:
129
129
130 # - This should be turned into a class which does proper namespace management,
130 # - This should be turned into a class which does proper namespace management,
131 # since the prompt specials need to be evaluated in a certain namespace.
131 # since the prompt specials need to be evaluated in a certain namespace.
132 # Currently it's just globals, which need to be managed manually by code
132 # Currently it's just globals, which need to be managed manually by code
133 # below.
133 # below.
134
134
135 # - I also need to split up the color schemes from the prompt specials
135 # - I also need to split up the color schemes from the prompt specials
136 # somehow. I don't have a clean design for that quite yet.
136 # somehow. I don't have a clean design for that quite yet.
137
137
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139
139
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
141 # /usr/home, but retain the $HOME variable as pointing to /home
141 # /usr/home, but retain the $HOME variable as pointing to /home
142 HOME = os.path.realpath(HOME)
142 HOME = os.path.realpath(HOME)
143
143
144 # We precompute a few more strings here for the prompt_specials, which are
144 # We precompute a few more strings here for the prompt_specials, which are
145 # fixed once ipython starts. This reduces the runtime overhead of computing
145 # fixed once ipython starts. This reduces the runtime overhead of computing
146 # prompt strings.
146 # prompt strings.
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150
150
151 # IronPython doesn't currently have os.getuid() even if
151 # IronPython doesn't currently have os.getuid() even if
152 # os.name == 'posix'; 2/8/2014
152 # os.name == 'posix'; 2/8/2014
153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
154
154
155 prompt_abbreviations = {
155 prompt_abbreviations = {
156 # Prompt/history count
156 # Prompt/history count
157 '%n' : '{color.number}' '{count}' '{color.prompt}',
157 '%n' : '{color.number}' '{count}' '{color.prompt}',
158 r'\#': '{color.number}' '{count}' '{color.prompt}',
158 r'\#': '{color.number}' '{count}' '{color.prompt}',
159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
160 # can get numbers displayed in whatever color they want.
160 # can get numbers displayed in whatever color they want.
161 r'\N': '{count}',
161 r'\N': '{count}',
162
162
163 # Prompt/history count, with the actual digits replaced by dots. Used
163 # Prompt/history count, with the actual digits replaced by dots. Used
164 # mainly in continuation prompts (prompt_in2)
164 # mainly in continuation prompts (prompt_in2)
165 r'\D': '{dots}',
165 r'\D': '{dots}',
166
166
167 # Current time
167 # Current time
168 r'\T' : '{time}',
168 r'\T' : '{time}',
169 # Current working directory
169 # Current working directory
170 r'\w': '{cwd}',
170 r'\w': '{cwd}',
171 # Basename of current working directory.
171 # Basename of current working directory.
172 # (use os.sep to make this portable across OSes)
172 # (use os.sep to make this portable across OSes)
173 r'\W' : '{cwd_last}',
173 r'\W' : '{cwd_last}',
174 # These X<N> are an extension to the normal bash prompts. They return
174 # These X<N> are an extension to the normal bash prompts. They return
175 # N terms of the path, after replacing $HOME with '~'
175 # N terms of the path, after replacing $HOME with '~'
176 r'\X0': '{cwd_x[0]}',
176 r'\X0': '{cwd_x[0]}',
177 r'\X1': '{cwd_x[1]}',
177 r'\X1': '{cwd_x[1]}',
178 r'\X2': '{cwd_x[2]}',
178 r'\X2': '{cwd_x[2]}',
179 r'\X3': '{cwd_x[3]}',
179 r'\X3': '{cwd_x[3]}',
180 r'\X4': '{cwd_x[4]}',
180 r'\X4': '{cwd_x[4]}',
181 r'\X5': '{cwd_x[5]}',
181 r'\X5': '{cwd_x[5]}',
182 # Y<N> are similar to X<N>, but they show '~' if it's the directory
182 # Y<N> are similar to X<N>, but they show '~' if it's the directory
183 # N+1 in the list. Somewhat like %cN in tcsh.
183 # N+1 in the list. Somewhat like %cN in tcsh.
184 r'\Y0': '{cwd_y[0]}',
184 r'\Y0': '{cwd_y[0]}',
185 r'\Y1': '{cwd_y[1]}',
185 r'\Y1': '{cwd_y[1]}',
186 r'\Y2': '{cwd_y[2]}',
186 r'\Y2': '{cwd_y[2]}',
187 r'\Y3': '{cwd_y[3]}',
187 r'\Y3': '{cwd_y[3]}',
188 r'\Y4': '{cwd_y[4]}',
188 r'\Y4': '{cwd_y[4]}',
189 r'\Y5': '{cwd_y[5]}',
189 r'\Y5': '{cwd_y[5]}',
190 # Hostname up to first .
190 # Hostname up to first .
191 r'\h': HOSTNAME_SHORT,
191 r'\h': HOSTNAME_SHORT,
192 # Full hostname
192 # Full hostname
193 r'\H': HOSTNAME,
193 r'\H': HOSTNAME,
194 # Username of current user
194 # Username of current user
195 r'\u': USER,
195 r'\u': USER,
196 # Escaped '\'
196 # Escaped '\'
197 '\\\\': '\\',
197 '\\\\': '\\',
198 # Newline
198 # Newline
199 r'\n': '\n',
199 r'\n': '\n',
200 # Carriage return
200 # Carriage return
201 r'\r': '\r',
201 r'\r': '\r',
202 # Release version
202 # Release version
203 r'\v': release.version,
203 r'\v': release.version,
204 # Root symbol ($ or #)
204 # Root symbol ($ or #)
205 r'\$': ROOT_SYMBOL,
205 r'\$': ROOT_SYMBOL,
206 }
206 }
207
207
208 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
209 # More utilities
209 # More utilities
210 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
211
211
212 def cwd_filt(depth):
212 def cwd_filt(depth):
213 """Return the last depth elements of the current working directory.
213 """Return the last depth elements of the current working directory.
214
214
215 $HOME is always replaced with '~'.
215 $HOME is always replaced with '~'.
216 If depth==0, the full path is returned."""
216 If depth==0, the full path is returned."""
217
217
218 cwd = py3compat.getcwd().replace(HOME,"~")
218 cwd = py3compat.getcwd().replace(HOME,"~")
219 out = os.sep.join(cwd.split(os.sep)[-depth:])
219 out = os.sep.join(cwd.split(os.sep)[-depth:])
220 return out or os.sep
220 return out or os.sep
221
221
222 def cwd_filt2(depth):
222 def cwd_filt2(depth):
223 """Return the last depth elements of the current working directory.
223 """Return the last depth elements of the current working directory.
224
224
225 $HOME is always replaced with '~'.
225 $HOME is always replaced with '~'.
226 If depth==0, the full path is returned."""
226 If depth==0, the full path is returned."""
227
227
228 full_cwd = py3compat.getcwd()
228 full_cwd = py3compat.getcwd()
229 cwd = full_cwd.replace(HOME,"~").split(os.sep)
229 cwd = full_cwd.replace(HOME,"~").split(os.sep)
230 if '~' in cwd and len(cwd) == depth+1:
230 if '~' in cwd and len(cwd) == depth+1:
231 depth += 1
231 depth += 1
232 drivepart = ''
232 drivepart = ''
233 if sys.platform == 'win32' and len(cwd) > depth:
233 if sys.platform == 'win32' and len(cwd) > depth:
234 drivepart = os.path.splitdrive(full_cwd)[0]
234 drivepart = os.path.splitdrive(full_cwd)[0]
235 out = drivepart + '/'.join(cwd[-depth:])
235 out = drivepart + '/'.join(cwd[-depth:])
236
236
237 return out or os.sep
237 return out or os.sep
238
238
239 #-----------------------------------------------------------------------------
239 #-----------------------------------------------------------------------------
240 # Prompt classes
240 # Prompt classes
241 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
242
242
243 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
243 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
244 'cwd': LazyEvaluate(py3compat.getcwd),
244 'cwd': LazyEvaluate(py3compat.getcwd),
245 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
245 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
246 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
246 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
247 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
247 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
248 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
248 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
249 }
249 }
250
250
251 def _lenlastline(s):
251 def _lenlastline(s):
252 """Get the length of the last line. More intelligent than
252 """Get the length of the last line. More intelligent than
253 len(s.splitlines()[-1]).
253 len(s.splitlines()[-1]).
254 """
254 """
255 if not s or s.endswith(('\n', '\r')):
255 if not s or s.endswith(('\n', '\r')):
256 return 0
256 return 0
257 return len(s.splitlines()[-1])
257 return len(s.splitlines()[-1])
258
258
259
259
260 class UserNSFormatter(Formatter):
260 class UserNSFormatter(Formatter):
261 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
261 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
262 def __init__(self, shell):
262 def __init__(self, shell):
263 self.shell = shell
263 self.shell = shell
264
264
265 def get_value(self, key, args, kwargs):
265 def get_value(self, key, args, kwargs):
266 # try regular formatting first:
266 # try regular formatting first:
267 try:
267 try:
268 return Formatter.get_value(self, key, args, kwargs)
268 return Formatter.get_value(self, key, args, kwargs)
269 except Exception:
269 except Exception:
270 pass
270 pass
271 # next, look in user_ns and builtins:
271 # next, look in user_ns and builtins:
272 for container in (self.shell.user_ns, __builtins__):
272 for container in (self.shell.user_ns, __builtins__):
273 if key in container:
273 if key in container:
274 return container[key]
274 return container[key]
275 # nothing found, put error message in its place
275 # nothing found, put error message in its place
276 return "<ERROR: '%s' not found>" % key
276 return "<ERROR: '%s' not found>" % key
277
277
278
278
279 class PromptManager(Configurable):
279 class PromptManager(Configurable):
280 """This is the primary interface for producing IPython's prompts."""
280 """This is the primary interface for producing IPython's prompts."""
281 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
281 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
282
282
283 color_scheme_table = Instance(coloransi.ColorSchemeTable)
283 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
284 color_scheme = Unicode('Linux', config=True)
284 color_scheme = Unicode('Linux', config=True)
285 def _color_scheme_changed(self, name, new_value):
285 def _color_scheme_changed(self, name, new_value):
286 self.color_scheme_table.set_active_scheme(new_value)
286 self.color_scheme_table.set_active_scheme(new_value)
287 for pname in ['in', 'in2', 'out', 'rewrite']:
287 for pname in ['in', 'in2', 'out', 'rewrite']:
288 # We need to recalculate the number of invisible characters
288 # We need to recalculate the number of invisible characters
289 self.update_prompt(pname)
289 self.update_prompt(pname)
290
290
291 lazy_evaluate_fields = Dict(help="""
291 lazy_evaluate_fields = Dict(help="""
292 This maps field names used in the prompt templates to functions which
292 This maps field names used in the prompt templates to functions which
293 will be called when the prompt is rendered. This allows us to include
293 will be called when the prompt is rendered. This allows us to include
294 things like the current time in the prompts. Functions are only called
294 things like the current time in the prompts. Functions are only called
295 if they are used in the prompt.
295 if they are used in the prompt.
296 """)
296 """)
297 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
297 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
298
298
299 in_template = Unicode('In [\\#]: ', config=True,
299 in_template = Unicode('In [\\#]: ', config=True,
300 help="Input prompt. '\\#' will be transformed to the prompt number")
300 help="Input prompt. '\\#' will be transformed to the prompt number")
301 in2_template = Unicode(' .\\D.: ', config=True,
301 in2_template = Unicode(' .\\D.: ', config=True,
302 help="Continuation prompt.")
302 help="Continuation prompt.")
303 out_template = Unicode('Out[\\#]: ', config=True,
303 out_template = Unicode('Out[\\#]: ', config=True,
304 help="Output prompt. '\\#' will be transformed to the prompt number")
304 help="Output prompt. '\\#' will be transformed to the prompt number")
305
305
306 justify = Bool(True, config=True, help="""
306 justify = Bool(True, config=True, help="""
307 If True (default), each prompt will be right-aligned with the
307 If True (default), each prompt will be right-aligned with the
308 preceding one.
308 preceding one.
309 """)
309 """)
310
310
311 # We actually store the expanded templates here:
311 # We actually store the expanded templates here:
312 templates = Dict()
312 templates = Dict()
313
313
314 # The number of characters in the last prompt rendered, not including
314 # The number of characters in the last prompt rendered, not including
315 # colour characters.
315 # colour characters.
316 width = Int()
316 width = Int()
317 txtwidth = Int() # Not including right-justification
317 txtwidth = Int() # Not including right-justification
318
318
319 # The number of characters in each prompt which don't contribute to width
319 # The number of characters in each prompt which don't contribute to width
320 invisible_chars = Dict()
320 invisible_chars = Dict()
321 def _invisible_chars_default(self):
321 def _invisible_chars_default(self):
322 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
322 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
323
323
324 def __init__(self, shell, **kwargs):
324 def __init__(self, shell, **kwargs):
325 super(PromptManager, self).__init__(shell=shell, **kwargs)
325 super(PromptManager, self).__init__(shell=shell, **kwargs)
326
326
327 # Prepare colour scheme table
327 # Prepare colour scheme table
328 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
328 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
329 PColLinux, PColLightBG], self.color_scheme)
329 PColLinux, PColLightBG], self.color_scheme)
330
330
331 self._formatter = UserNSFormatter(shell)
331 self._formatter = UserNSFormatter(shell)
332 # Prepare templates & numbers of invisible characters
332 # Prepare templates & numbers of invisible characters
333 self.update_prompt('in', self.in_template)
333 self.update_prompt('in', self.in_template)
334 self.update_prompt('in2', self.in2_template)
334 self.update_prompt('in2', self.in2_template)
335 self.update_prompt('out', self.out_template)
335 self.update_prompt('out', self.out_template)
336 self.update_prompt('rewrite')
336 self.update_prompt('rewrite')
337 self.on_trait_change(self._update_prompt_trait, ['in_template',
337 self.on_trait_change(self._update_prompt_trait, ['in_template',
338 'in2_template', 'out_template'])
338 'in2_template', 'out_template'])
339
339
340 def update_prompt(self, name, new_template=None):
340 def update_prompt(self, name, new_template=None):
341 """This is called when a prompt template is updated. It processes
341 """This is called when a prompt template is updated. It processes
342 abbreviations used in the prompt template (like \#) and calculates how
342 abbreviations used in the prompt template (like \#) and calculates how
343 many invisible characters (ANSI colour escapes) the resulting prompt
343 many invisible characters (ANSI colour escapes) the resulting prompt
344 contains.
344 contains.
345
345
346 It is also called for each prompt on changing the colour scheme. In both
346 It is also called for each prompt on changing the colour scheme. In both
347 cases, traitlets should take care of calling this automatically.
347 cases, traitlets should take care of calling this automatically.
348 """
348 """
349 if new_template is not None:
349 if new_template is not None:
350 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
350 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
351 # We count invisible characters (colour escapes) on the last line of the
351 # We count invisible characters (colour escapes) on the last line of the
352 # prompt, to calculate the width for lining up subsequent prompts.
352 # prompt, to calculate the width for lining up subsequent prompts.
353 invis_chars = _lenlastline(self._render(name, color=True)) - \
353 invis_chars = _lenlastline(self._render(name, color=True)) - \
354 _lenlastline(self._render(name, color=False))
354 _lenlastline(self._render(name, color=False))
355 self.invisible_chars[name] = invis_chars
355 self.invisible_chars[name] = invis_chars
356
356
357 def _update_prompt_trait(self, traitname, new_template):
357 def _update_prompt_trait(self, traitname, new_template):
358 name = traitname[:-9] # Cut off '_template'
358 name = traitname[:-9] # Cut off '_template'
359 self.update_prompt(name, new_template)
359 self.update_prompt(name, new_template)
360
360
361 def _render(self, name, color=True, **kwargs):
361 def _render(self, name, color=True, **kwargs):
362 """Render but don't justify, or update the width or txtwidth attributes.
362 """Render but don't justify, or update the width or txtwidth attributes.
363 """
363 """
364 if name == 'rewrite':
364 if name == 'rewrite':
365 return self._render_rewrite(color=color)
365 return self._render_rewrite(color=color)
366
366
367 if color:
367 if color:
368 scheme = self.color_scheme_table.active_colors
368 scheme = self.color_scheme_table.active_colors
369 if name=='out':
369 if name=='out':
370 colors = color_lists['normal']
370 colors = color_lists['normal']
371 colors.number, colors.prompt, colors.normal = \
371 colors.number, colors.prompt, colors.normal = \
372 scheme.out_number, scheme.out_prompt, scheme.normal
372 scheme.out_number, scheme.out_prompt, scheme.normal
373 else:
373 else:
374 colors = color_lists['inp']
374 colors = color_lists['inp']
375 colors.number, colors.prompt, colors.normal = \
375 colors.number, colors.prompt, colors.normal = \
376 scheme.in_number, scheme.in_prompt, scheme.in_normal
376 scheme.in_number, scheme.in_prompt, scheme.in_normal
377 if name=='in2':
377 if name=='in2':
378 colors.prompt = scheme.in_prompt2
378 colors.prompt = scheme.in_prompt2
379 else:
379 else:
380 # No color
380 # No color
381 colors = color_lists['nocolor']
381 colors = color_lists['nocolor']
382 colors.number, colors.prompt, colors.normal = '', '', ''
382 colors.number, colors.prompt, colors.normal = '', '', ''
383
383
384 count = self.shell.execution_count # Shorthand
384 count = self.shell.execution_count # Shorthand
385 # Build the dictionary to be passed to string formatting
385 # Build the dictionary to be passed to string formatting
386 fmtargs = dict(color=colors, count=count,
386 fmtargs = dict(color=colors, count=count,
387 dots="."*len(str(count)),
387 dots="."*len(str(count)),
388 width=self.width, txtwidth=self.txtwidth )
388 width=self.width, txtwidth=self.txtwidth )
389 fmtargs.update(self.lazy_evaluate_fields)
389 fmtargs.update(self.lazy_evaluate_fields)
390 fmtargs.update(kwargs)
390 fmtargs.update(kwargs)
391
391
392 # Prepare the prompt
392 # Prepare the prompt
393 prompt = colors.prompt + self.templates[name] + colors.normal
393 prompt = colors.prompt + self.templates[name] + colors.normal
394
394
395 # Fill in required fields
395 # Fill in required fields
396 return self._formatter.format(prompt, **fmtargs)
396 return self._formatter.format(prompt, **fmtargs)
397
397
398 def _render_rewrite(self, color=True):
398 def _render_rewrite(self, color=True):
399 """Render the ---> rewrite prompt."""
399 """Render the ---> rewrite prompt."""
400 if color:
400 if color:
401 scheme = self.color_scheme_table.active_colors
401 scheme = self.color_scheme_table.active_colors
402 # We need a non-input version of these escapes
402 # We need a non-input version of these escapes
403 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
403 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
404 color_normal = scheme.normal
404 color_normal = scheme.normal
405 else:
405 else:
406 color_prompt, color_normal = '', ''
406 color_prompt, color_normal = '', ''
407
407
408 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
408 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
409
409
410 def render(self, name, color=True, just=None, **kwargs):
410 def render(self, name, color=True, just=None, **kwargs):
411 """
411 """
412 Render the selected prompt.
412 Render the selected prompt.
413
413
414 Parameters
414 Parameters
415 ----------
415 ----------
416 name : str
416 name : str
417 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
417 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
418 color : bool
418 color : bool
419 If True (default), include ANSI escape sequences for a coloured prompt.
419 If True (default), include ANSI escape sequences for a coloured prompt.
420 just : bool
420 just : bool
421 If True, justify the prompt to the width of the last prompt. The
421 If True, justify the prompt to the width of the last prompt. The
422 default is stored in self.justify.
422 default is stored in self.justify.
423 **kwargs :
423 **kwargs :
424 Additional arguments will be passed to the string formatting operation,
424 Additional arguments will be passed to the string formatting operation,
425 so they can override the values that would otherwise fill in the
425 so they can override the values that would otherwise fill in the
426 template.
426 template.
427
427
428 Returns
428 Returns
429 -------
429 -------
430 A string containing the rendered prompt.
430 A string containing the rendered prompt.
431 """
431 """
432 res = self._render(name, color=color, **kwargs)
432 res = self._render(name, color=color, **kwargs)
433
433
434 # Handle justification of prompt
434 # Handle justification of prompt
435 invis_chars = self.invisible_chars[name] if color else 0
435 invis_chars = self.invisible_chars[name] if color else 0
436 self.txtwidth = _lenlastline(res) - invis_chars
436 self.txtwidth = _lenlastline(res) - invis_chars
437 just = self.justify if (just is None) else just
437 just = self.justify if (just is None) else just
438 # If the prompt spans more than one line, don't try to justify it:
438 # If the prompt spans more than one line, don't try to justify it:
439 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
439 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
440 res = res.rjust(self.width + invis_chars)
440 res = res.rjust(self.width + invis_chars)
441 self.width = _lenlastline(res) - invis_chars
441 self.width = _lenlastline(res) - invis_chars
442 return res
442 return res
@@ -1,432 +1,433 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A mixin for :class:`~IPython.core.application.Application` classes that
3 A mixin for :class:`~IPython.core.application.Application` classes that
4 launch InteractiveShell instances, load extensions, etc.
4 launch InteractiveShell instances, load extensions, etc.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 import glob
13 import glob
14 import os
14 import os
15 import sys
15 import sys
16
16
17 from IPython.config.application import boolean_flag
17 from IPython.config.application import boolean_flag
18 from IPython.config.configurable import Configurable
18 from IPython.config.configurable import Configurable
19 from IPython.config.loader import Config
19 from IPython.config.loader import Config
20 from IPython.core import pylabtools
20 from IPython.core import pylabtools
21 from IPython.utils import py3compat
21 from IPython.utils import py3compat
22 from IPython.utils.contexts import preserve_keys
22 from IPython.utils.contexts import preserve_keys
23 from IPython.utils.path import filefind
23 from IPython.utils.path import filefind
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import (
25 Unicode, Instance, List, Bool, CaselessStrEnum
25 Unicode, Instance, List, Bool, CaselessStrEnum
26 )
26 )
27 from IPython.lib.inputhook import guis
27 from IPython.lib.inputhook import guis
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Aliases and Flags
30 # Aliases and Flags
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 gui_keys = tuple(sorted([ key for key in guis if key is not None ]))
33 gui_keys = tuple(sorted([ key for key in guis if key is not None ]))
34
34
35 backend_keys = sorted(pylabtools.backends.keys())
35 backend_keys = sorted(pylabtools.backends.keys())
36 backend_keys.insert(0, 'auto')
36 backend_keys.insert(0, 'auto')
37
37
38 shell_flags = {}
38 shell_flags = {}
39
39
40 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
40 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
41 addflag('autoindent', 'InteractiveShell.autoindent',
41 addflag('autoindent', 'InteractiveShell.autoindent',
42 'Turn on autoindenting.', 'Turn off autoindenting.'
42 'Turn on autoindenting.', 'Turn off autoindenting.'
43 )
43 )
44 addflag('automagic', 'InteractiveShell.automagic',
44 addflag('automagic', 'InteractiveShell.automagic',
45 """Turn on the auto calling of magic commands. Type %%magic at the
45 """Turn on the auto calling of magic commands. Type %%magic at the
46 IPython prompt for more information.""",
46 IPython prompt for more information.""",
47 'Turn off the auto calling of magic commands.'
47 'Turn off the auto calling of magic commands.'
48 )
48 )
49 addflag('pdb', 'InteractiveShell.pdb',
49 addflag('pdb', 'InteractiveShell.pdb',
50 "Enable auto calling the pdb debugger after every exception.",
50 "Enable auto calling the pdb debugger after every exception.",
51 "Disable auto calling the pdb debugger after every exception."
51 "Disable auto calling the pdb debugger after every exception."
52 )
52 )
53 # pydb flag doesn't do any config, as core.debugger switches on import,
53 # pydb flag doesn't do any config, as core.debugger switches on import,
54 # which is before parsing. This just allows the flag to be passed.
54 # which is before parsing. This just allows the flag to be passed.
55 shell_flags.update(dict(
55 shell_flags.update(dict(
56 pydb = ({},
56 pydb = ({},
57 """Use the third party 'pydb' package as debugger, instead of pdb.
57 """Use the third party 'pydb' package as debugger, instead of pdb.
58 Requires that pydb is installed."""
58 Requires that pydb is installed."""
59 )
59 )
60 ))
60 ))
61 addflag('pprint', 'PlainTextFormatter.pprint',
61 addflag('pprint', 'PlainTextFormatter.pprint',
62 "Enable auto pretty printing of results.",
62 "Enable auto pretty printing of results.",
63 "Disable auto pretty printing of results."
63 "Disable auto pretty printing of results."
64 )
64 )
65 addflag('color-info', 'InteractiveShell.color_info',
65 addflag('color-info', 'InteractiveShell.color_info',
66 """IPython can display information about objects via a set of functions,
66 """IPython can display information about objects via a set of functions,
67 and optionally can use colors for this, syntax highlighting
67 and optionally can use colors for this, syntax highlighting
68 source code and various other elements. This is on by default, but can cause
68 source code and various other elements. This is on by default, but can cause
69 problems with some pagers. If you see such problems, you can disable the
69 problems with some pagers. If you see such problems, you can disable the
70 colours.""",
70 colours.""",
71 "Disable using colors for info related things."
71 "Disable using colors for info related things."
72 )
72 )
73 addflag('deep-reload', 'InteractiveShell.deep_reload',
73 addflag('deep-reload', 'InteractiveShell.deep_reload',
74 """Enable deep (recursive) reloading by default. IPython can use the
74 """Enable deep (recursive) reloading by default. IPython can use the
75 deep_reload module which reloads changes in modules recursively (it
75 deep_reload module which reloads changes in modules recursively (it
76 replaces the reload() function, so you don't need to change anything to
76 replaces the reload() function, so you don't need to change anything to
77 use it). deep_reload() forces a full reload of modules whose code may
77 use it). deep_reload() forces a full reload of modules whose code may
78 have changed, which the default reload() function does not. When
78 have changed, which the default reload() function does not. When
79 deep_reload is off, IPython will use the normal reload(), but
79 deep_reload is off, IPython will use the normal reload(), but
80 deep_reload will still be available as dreload(). This feature is off
80 deep_reload will still be available as dreload(). This feature is off
81 by default [which means that you have both normal reload() and
81 by default [which means that you have both normal reload() and
82 dreload()].""",
82 dreload()].""",
83 "Disable deep (recursive) reloading by default."
83 "Disable deep (recursive) reloading by default."
84 )
84 )
85 nosep_config = Config()
85 nosep_config = Config()
86 nosep_config.InteractiveShell.separate_in = ''
86 nosep_config.InteractiveShell.separate_in = ''
87 nosep_config.InteractiveShell.separate_out = ''
87 nosep_config.InteractiveShell.separate_out = ''
88 nosep_config.InteractiveShell.separate_out2 = ''
88 nosep_config.InteractiveShell.separate_out2 = ''
89
89
90 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
90 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
91 shell_flags['pylab'] = (
91 shell_flags['pylab'] = (
92 {'InteractiveShellApp' : {'pylab' : 'auto'}},
92 {'InteractiveShellApp' : {'pylab' : 'auto'}},
93 """Pre-load matplotlib and numpy for interactive use with
93 """Pre-load matplotlib and numpy for interactive use with
94 the default matplotlib backend."""
94 the default matplotlib backend."""
95 )
95 )
96 shell_flags['matplotlib'] = (
96 shell_flags['matplotlib'] = (
97 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
97 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
98 """Configure matplotlib for interactive use with
98 """Configure matplotlib for interactive use with
99 the default matplotlib backend."""
99 the default matplotlib backend."""
100 )
100 )
101
101
102 # it's possible we don't want short aliases for *all* of these:
102 # it's possible we don't want short aliases for *all* of these:
103 shell_aliases = dict(
103 shell_aliases = dict(
104 autocall='InteractiveShell.autocall',
104 autocall='InteractiveShell.autocall',
105 colors='InteractiveShell.colors',
105 colors='InteractiveShell.colors',
106 logfile='InteractiveShell.logfile',
106 logfile='InteractiveShell.logfile',
107 logappend='InteractiveShell.logappend',
107 logappend='InteractiveShell.logappend',
108 c='InteractiveShellApp.code_to_run',
108 c='InteractiveShellApp.code_to_run',
109 m='InteractiveShellApp.module_to_run',
109 m='InteractiveShellApp.module_to_run',
110 ext='InteractiveShellApp.extra_extension',
110 ext='InteractiveShellApp.extra_extension',
111 gui='InteractiveShellApp.gui',
111 gui='InteractiveShellApp.gui',
112 pylab='InteractiveShellApp.pylab',
112 pylab='InteractiveShellApp.pylab',
113 matplotlib='InteractiveShellApp.matplotlib',
113 matplotlib='InteractiveShellApp.matplotlib',
114 )
114 )
115 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
115 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
116
116
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118 # Main classes and functions
118 # Main classes and functions
119 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
120
120
121 class InteractiveShellApp(Configurable):
121 class InteractiveShellApp(Configurable):
122 """A Mixin for applications that start InteractiveShell instances.
122 """A Mixin for applications that start InteractiveShell instances.
123
123
124 Provides configurables for loading extensions and executing files
124 Provides configurables for loading extensions and executing files
125 as part of configuring a Shell environment.
125 as part of configuring a Shell environment.
126
126
127 The following methods should be called by the :meth:`initialize` method
127 The following methods should be called by the :meth:`initialize` method
128 of the subclass:
128 of the subclass:
129
129
130 - :meth:`init_path`
130 - :meth:`init_path`
131 - :meth:`init_shell` (to be implemented by the subclass)
131 - :meth:`init_shell` (to be implemented by the subclass)
132 - :meth:`init_gui_pylab`
132 - :meth:`init_gui_pylab`
133 - :meth:`init_extensions`
133 - :meth:`init_extensions`
134 - :meth:`init_code`
134 - :meth:`init_code`
135 """
135 """
136 extensions = List(Unicode, config=True,
136 extensions = List(Unicode, config=True,
137 help="A list of dotted module names of IPython extensions to load."
137 help="A list of dotted module names of IPython extensions to load."
138 )
138 )
139 extra_extension = Unicode('', config=True,
139 extra_extension = Unicode('', config=True,
140 help="dotted module name of an IPython extension to load."
140 help="dotted module name of an IPython extension to load."
141 )
141 )
142
142
143 reraise_ipython_extension_failures = Bool(
143 reraise_ipython_extension_failures = Bool(
144 False,
144 False,
145 config=True,
145 config=True,
146 help="Reraise exceptions encountered loading IPython extensions?",
146 help="Reraise exceptions encountered loading IPython extensions?",
147 )
147 )
148
148
149 # Extensions that are always loaded (not configurable)
149 # Extensions that are always loaded (not configurable)
150 default_extensions = List(Unicode, [u'storemagic'], config=False)
150 default_extensions = List(Unicode, [u'storemagic'], config=False)
151
151
152 hide_initial_ns = Bool(True, config=True,
152 hide_initial_ns = Bool(True, config=True,
153 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
153 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
154 be hidden from tools like %who?"""
154 be hidden from tools like %who?"""
155 )
155 )
156
156
157 exec_files = List(Unicode, config=True,
157 exec_files = List(Unicode, config=True,
158 help="""List of files to run at IPython startup."""
158 help="""List of files to run at IPython startup."""
159 )
159 )
160 exec_PYTHONSTARTUP = Bool(True, config=True,
160 exec_PYTHONSTARTUP = Bool(True, config=True,
161 help="""Run the file referenced by the PYTHONSTARTUP environment
161 help="""Run the file referenced by the PYTHONSTARTUP environment
162 variable at IPython startup."""
162 variable at IPython startup."""
163 )
163 )
164 file_to_run = Unicode('', config=True,
164 file_to_run = Unicode('', config=True,
165 help="""A file to be run""")
165 help="""A file to be run""")
166
166
167 exec_lines = List(Unicode, config=True,
167 exec_lines = List(Unicode, config=True,
168 help="""lines of code to run at IPython startup."""
168 help="""lines of code to run at IPython startup."""
169 )
169 )
170 code_to_run = Unicode('', config=True,
170 code_to_run = Unicode('', config=True,
171 help="Execute the given command string."
171 help="Execute the given command string."
172 )
172 )
173 module_to_run = Unicode('', config=True,
173 module_to_run = Unicode('', config=True,
174 help="Run the module as a script."
174 help="Run the module as a script."
175 )
175 )
176 gui = CaselessStrEnum(gui_keys, config=True, allow_none=True,
176 gui = CaselessStrEnum(gui_keys, config=True, allow_none=True,
177 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
177 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
178 )
178 )
179 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
179 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
180 config=True,
180 config=True,
181 help="""Configure matplotlib for interactive use with
181 help="""Configure matplotlib for interactive use with
182 the default matplotlib backend."""
182 the default matplotlib backend."""
183 )
183 )
184 pylab = CaselessStrEnum(backend_keys, allow_none=True,
184 pylab = CaselessStrEnum(backend_keys, allow_none=True,
185 config=True,
185 config=True,
186 help="""Pre-load matplotlib and numpy for interactive use,
186 help="""Pre-load matplotlib and numpy for interactive use,
187 selecting a particular matplotlib backend and loop integration.
187 selecting a particular matplotlib backend and loop integration.
188 """
188 """
189 )
189 )
190 pylab_import_all = Bool(True, config=True,
190 pylab_import_all = Bool(True, config=True,
191 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
191 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
192 and an ``import *`` is done from numpy and pylab, when using pylab mode.
192 and an ``import *`` is done from numpy and pylab, when using pylab mode.
193
193
194 When False, pylab mode should not import any names into the user namespace.
194 When False, pylab mode should not import any names into the user namespace.
195 """
195 """
196 )
196 )
197 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
197 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
198 allow_none=True)
198
199
199 user_ns = Instance(dict, args=None, allow_none=True)
200 user_ns = Instance(dict, args=None, allow_none=True)
200 def _user_ns_changed(self, name, old, new):
201 def _user_ns_changed(self, name, old, new):
201 if self.shell is not None:
202 if self.shell is not None:
202 self.shell.user_ns = new
203 self.shell.user_ns = new
203 self.shell.init_user_ns()
204 self.shell.init_user_ns()
204
205
205 def init_path(self):
206 def init_path(self):
206 """Add current working directory, '', to sys.path"""
207 """Add current working directory, '', to sys.path"""
207 if sys.path[0] != '':
208 if sys.path[0] != '':
208 sys.path.insert(0, '')
209 sys.path.insert(0, '')
209
210
210 def init_shell(self):
211 def init_shell(self):
211 raise NotImplementedError("Override in subclasses")
212 raise NotImplementedError("Override in subclasses")
212
213
213 def init_gui_pylab(self):
214 def init_gui_pylab(self):
214 """Enable GUI event loop integration, taking pylab into account."""
215 """Enable GUI event loop integration, taking pylab into account."""
215 enable = False
216 enable = False
216 shell = self.shell
217 shell = self.shell
217 if self.pylab:
218 if self.pylab:
218 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
219 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
219 key = self.pylab
220 key = self.pylab
220 elif self.matplotlib:
221 elif self.matplotlib:
221 enable = shell.enable_matplotlib
222 enable = shell.enable_matplotlib
222 key = self.matplotlib
223 key = self.matplotlib
223 elif self.gui:
224 elif self.gui:
224 enable = shell.enable_gui
225 enable = shell.enable_gui
225 key = self.gui
226 key = self.gui
226
227
227 if not enable:
228 if not enable:
228 return
229 return
229
230
230 try:
231 try:
231 r = enable(key)
232 r = enable(key)
232 except ImportError:
233 except ImportError:
233 self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?")
234 self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?")
234 self.shell.showtraceback()
235 self.shell.showtraceback()
235 return
236 return
236 except Exception:
237 except Exception:
237 self.log.warn("GUI event loop or pylab initialization failed")
238 self.log.warn("GUI event loop or pylab initialization failed")
238 self.shell.showtraceback()
239 self.shell.showtraceback()
239 return
240 return
240
241
241 if isinstance(r, tuple):
242 if isinstance(r, tuple):
242 gui, backend = r[:2]
243 gui, backend = r[:2]
243 self.log.info("Enabling GUI event loop integration, "
244 self.log.info("Enabling GUI event loop integration, "
244 "eventloop=%s, matplotlib=%s", gui, backend)
245 "eventloop=%s, matplotlib=%s", gui, backend)
245 if key == "auto":
246 if key == "auto":
246 print("Using matplotlib backend: %s" % backend)
247 print("Using matplotlib backend: %s" % backend)
247 else:
248 else:
248 gui = r
249 gui = r
249 self.log.info("Enabling GUI event loop integration, "
250 self.log.info("Enabling GUI event loop integration, "
250 "eventloop=%s", gui)
251 "eventloop=%s", gui)
251
252
252 def init_extensions(self):
253 def init_extensions(self):
253 """Load all IPython extensions in IPythonApp.extensions.
254 """Load all IPython extensions in IPythonApp.extensions.
254
255
255 This uses the :meth:`ExtensionManager.load_extensions` to load all
256 This uses the :meth:`ExtensionManager.load_extensions` to load all
256 the extensions listed in ``self.extensions``.
257 the extensions listed in ``self.extensions``.
257 """
258 """
258 try:
259 try:
259 self.log.debug("Loading IPython extensions...")
260 self.log.debug("Loading IPython extensions...")
260 extensions = self.default_extensions + self.extensions
261 extensions = self.default_extensions + self.extensions
261 if self.extra_extension:
262 if self.extra_extension:
262 extensions.append(self.extra_extension)
263 extensions.append(self.extra_extension)
263 for ext in extensions:
264 for ext in extensions:
264 try:
265 try:
265 self.log.info("Loading IPython extension: %s" % ext)
266 self.log.info("Loading IPython extension: %s" % ext)
266 self.shell.extension_manager.load_extension(ext)
267 self.shell.extension_manager.load_extension(ext)
267 except:
268 except:
268 if self.reraise_ipython_extension_failures:
269 if self.reraise_ipython_extension_failures:
269 raise
270 raise
270 msg = ("Error in loading extension: {ext}\n"
271 msg = ("Error in loading extension: {ext}\n"
271 "Check your config files in {location}".format(
272 "Check your config files in {location}".format(
272 ext=ext,
273 ext=ext,
273 location=self.profile_dir.location
274 location=self.profile_dir.location
274 ))
275 ))
275 self.log.warn(msg, exc_info=True)
276 self.log.warn(msg, exc_info=True)
276 except:
277 except:
277 if self.reraise_ipython_extension_failures:
278 if self.reraise_ipython_extension_failures:
278 raise
279 raise
279 self.log.warn("Unknown error in loading extensions:", exc_info=True)
280 self.log.warn("Unknown error in loading extensions:", exc_info=True)
280
281
281 def init_code(self):
282 def init_code(self):
282 """run the pre-flight code, specified via exec_lines"""
283 """run the pre-flight code, specified via exec_lines"""
283 self._run_startup_files()
284 self._run_startup_files()
284 self._run_exec_lines()
285 self._run_exec_lines()
285 self._run_exec_files()
286 self._run_exec_files()
286
287
287 # Hide variables defined here from %who etc.
288 # Hide variables defined here from %who etc.
288 if self.hide_initial_ns:
289 if self.hide_initial_ns:
289 self.shell.user_ns_hidden.update(self.shell.user_ns)
290 self.shell.user_ns_hidden.update(self.shell.user_ns)
290
291
291 # command-line execution (ipython -i script.py, ipython -m module)
292 # command-line execution (ipython -i script.py, ipython -m module)
292 # should *not* be excluded from %whos
293 # should *not* be excluded from %whos
293 self._run_cmd_line_code()
294 self._run_cmd_line_code()
294 self._run_module()
295 self._run_module()
295
296
296 # flush output, so itwon't be attached to the first cell
297 # flush output, so itwon't be attached to the first cell
297 sys.stdout.flush()
298 sys.stdout.flush()
298 sys.stderr.flush()
299 sys.stderr.flush()
299
300
300 def _run_exec_lines(self):
301 def _run_exec_lines(self):
301 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
302 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
302 if not self.exec_lines:
303 if not self.exec_lines:
303 return
304 return
304 try:
305 try:
305 self.log.debug("Running code from IPythonApp.exec_lines...")
306 self.log.debug("Running code from IPythonApp.exec_lines...")
306 for line in self.exec_lines:
307 for line in self.exec_lines:
307 try:
308 try:
308 self.log.info("Running code in user namespace: %s" %
309 self.log.info("Running code in user namespace: %s" %
309 line)
310 line)
310 self.shell.run_cell(line, store_history=False)
311 self.shell.run_cell(line, store_history=False)
311 except:
312 except:
312 self.log.warn("Error in executing line in user "
313 self.log.warn("Error in executing line in user "
313 "namespace: %s" % line)
314 "namespace: %s" % line)
314 self.shell.showtraceback()
315 self.shell.showtraceback()
315 except:
316 except:
316 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
317 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
317 self.shell.showtraceback()
318 self.shell.showtraceback()
318
319
319 def _exec_file(self, fname, shell_futures=False):
320 def _exec_file(self, fname, shell_futures=False):
320 try:
321 try:
321 full_filename = filefind(fname, [u'.', self.ipython_dir])
322 full_filename = filefind(fname, [u'.', self.ipython_dir])
322 except IOError as e:
323 except IOError as e:
323 self.log.warn("File not found: %r"%fname)
324 self.log.warn("File not found: %r"%fname)
324 return
325 return
325 # Make sure that the running script gets a proper sys.argv as if it
326 # Make sure that the running script gets a proper sys.argv as if it
326 # were run from a system shell.
327 # were run from a system shell.
327 save_argv = sys.argv
328 save_argv = sys.argv
328 sys.argv = [full_filename] + self.extra_args[1:]
329 sys.argv = [full_filename] + self.extra_args[1:]
329 # protect sys.argv from potential unicode strings on Python 2:
330 # protect sys.argv from potential unicode strings on Python 2:
330 if not py3compat.PY3:
331 if not py3compat.PY3:
331 sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ]
332 sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ]
332 try:
333 try:
333 if os.path.isfile(full_filename):
334 if os.path.isfile(full_filename):
334 self.log.info("Running file in user namespace: %s" %
335 self.log.info("Running file in user namespace: %s" %
335 full_filename)
336 full_filename)
336 # Ensure that __file__ is always defined to match Python
337 # Ensure that __file__ is always defined to match Python
337 # behavior.
338 # behavior.
338 with preserve_keys(self.shell.user_ns, '__file__'):
339 with preserve_keys(self.shell.user_ns, '__file__'):
339 self.shell.user_ns['__file__'] = fname
340 self.shell.user_ns['__file__'] = fname
340 if full_filename.endswith('.ipy'):
341 if full_filename.endswith('.ipy'):
341 self.shell.safe_execfile_ipy(full_filename,
342 self.shell.safe_execfile_ipy(full_filename,
342 shell_futures=shell_futures)
343 shell_futures=shell_futures)
343 else:
344 else:
344 # default to python, even without extension
345 # default to python, even without extension
345 self.shell.safe_execfile(full_filename,
346 self.shell.safe_execfile(full_filename,
346 self.shell.user_ns,
347 self.shell.user_ns,
347 shell_futures=shell_futures)
348 shell_futures=shell_futures)
348 finally:
349 finally:
349 sys.argv = save_argv
350 sys.argv = save_argv
350
351
351 def _run_startup_files(self):
352 def _run_startup_files(self):
352 """Run files from profile startup directory"""
353 """Run files from profile startup directory"""
353 startup_dir = self.profile_dir.startup_dir
354 startup_dir = self.profile_dir.startup_dir
354 startup_files = []
355 startup_files = []
355
356
356 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
357 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
357 not (self.file_to_run or self.code_to_run or self.module_to_run):
358 not (self.file_to_run or self.code_to_run or self.module_to_run):
358 python_startup = os.environ['PYTHONSTARTUP']
359 python_startup = os.environ['PYTHONSTARTUP']
359 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
360 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
360 try:
361 try:
361 self._exec_file(python_startup)
362 self._exec_file(python_startup)
362 except:
363 except:
363 self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
364 self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
364 self.shell.showtraceback()
365 self.shell.showtraceback()
365 finally:
366 finally:
366 # Many PYTHONSTARTUP files set up the readline completions,
367 # Many PYTHONSTARTUP files set up the readline completions,
367 # but this is often at odds with IPython's own completions.
368 # but this is often at odds with IPython's own completions.
368 # Do not allow PYTHONSTARTUP to set up readline.
369 # Do not allow PYTHONSTARTUP to set up readline.
369 if self.shell.has_readline:
370 if self.shell.has_readline:
370 self.shell.set_readline_completer()
371 self.shell.set_readline_completer()
371
372
372 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
373 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
373 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
374 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
374 if not startup_files:
375 if not startup_files:
375 return
376 return
376
377
377 self.log.debug("Running startup files from %s...", startup_dir)
378 self.log.debug("Running startup files from %s...", startup_dir)
378 try:
379 try:
379 for fname in sorted(startup_files):
380 for fname in sorted(startup_files):
380 self._exec_file(fname)
381 self._exec_file(fname)
381 except:
382 except:
382 self.log.warn("Unknown error in handling startup files:")
383 self.log.warn("Unknown error in handling startup files:")
383 self.shell.showtraceback()
384 self.shell.showtraceback()
384
385
385 def _run_exec_files(self):
386 def _run_exec_files(self):
386 """Run files from IPythonApp.exec_files"""
387 """Run files from IPythonApp.exec_files"""
387 if not self.exec_files:
388 if not self.exec_files:
388 return
389 return
389
390
390 self.log.debug("Running files in IPythonApp.exec_files...")
391 self.log.debug("Running files in IPythonApp.exec_files...")
391 try:
392 try:
392 for fname in self.exec_files:
393 for fname in self.exec_files:
393 self._exec_file(fname)
394 self._exec_file(fname)
394 except:
395 except:
395 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
396 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
396 self.shell.showtraceback()
397 self.shell.showtraceback()
397
398
398 def _run_cmd_line_code(self):
399 def _run_cmd_line_code(self):
399 """Run code or file specified at the command-line"""
400 """Run code or file specified at the command-line"""
400 if self.code_to_run:
401 if self.code_to_run:
401 line = self.code_to_run
402 line = self.code_to_run
402 try:
403 try:
403 self.log.info("Running code given at command line (c=): %s" %
404 self.log.info("Running code given at command line (c=): %s" %
404 line)
405 line)
405 self.shell.run_cell(line, store_history=False)
406 self.shell.run_cell(line, store_history=False)
406 except:
407 except:
407 self.log.warn("Error in executing line in user namespace: %s" %
408 self.log.warn("Error in executing line in user namespace: %s" %
408 line)
409 line)
409 self.shell.showtraceback()
410 self.shell.showtraceback()
410
411
411 # Like Python itself, ignore the second if the first of these is present
412 # Like Python itself, ignore the second if the first of these is present
412 elif self.file_to_run:
413 elif self.file_to_run:
413 fname = self.file_to_run
414 fname = self.file_to_run
414 try:
415 try:
415 self._exec_file(fname, shell_futures=True)
416 self._exec_file(fname, shell_futures=True)
416 except:
417 except:
417 self.log.warn("Error in executing file in user namespace: %s" %
418 self.log.warn("Error in executing file in user namespace: %s" %
418 fname)
419 fname)
419 self.shell.showtraceback()
420 self.shell.showtraceback()
420
421
421 def _run_module(self):
422 def _run_module(self):
422 """Run module specified at the command-line."""
423 """Run module specified at the command-line."""
423 if self.module_to_run:
424 if self.module_to_run:
424 # Make sure that the module gets a proper sys.argv as if it were
425 # Make sure that the module gets a proper sys.argv as if it were
425 # run using `python -m`.
426 # run using `python -m`.
426 save_argv = sys.argv
427 save_argv = sys.argv
427 sys.argv = [sys.executable] + self.extra_args
428 sys.argv = [sys.executable] + self.extra_args
428 try:
429 try:
429 self.shell.safe_run_module(self.module_to_run,
430 self.shell.safe_run_module(self.module_to_run,
430 self.shell.user_ns)
431 self.shell.user_ns)
431 finally:
432 finally:
432 sys.argv = save_argv
433 sys.argv = save_argv
@@ -1,1127 +1,1127 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server."""
2 """A tornado based IPython notebook server."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import importlib
12 import importlib
13 import io
13 import io
14 import json
14 import json
15 import logging
15 import logging
16 import os
16 import os
17 import random
17 import random
18 import re
18 import re
19 import select
19 import select
20 import signal
20 import signal
21 import socket
21 import socket
22 import sys
22 import sys
23 import threading
23 import threading
24 import webbrowser
24 import webbrowser
25
25
26
26
27 # check for pyzmq
27 # check for pyzmq
28 from IPython.utils.zmqrelated import check_for_zmq
28 from IPython.utils.zmqrelated import check_for_zmq
29 check_for_zmq('13', 'IPython.html')
29 check_for_zmq('13', 'IPython.html')
30
30
31 from jinja2 import Environment, FileSystemLoader
31 from jinja2 import Environment, FileSystemLoader
32
32
33 # Install the pyzmq ioloop. This has to be done before anything else from
33 # Install the pyzmq ioloop. This has to be done before anything else from
34 # tornado is imported.
34 # tornado is imported.
35 from zmq.eventloop import ioloop
35 from zmq.eventloop import ioloop
36 ioloop.install()
36 ioloop.install()
37
37
38 # check for tornado 3.1.0
38 # check for tornado 3.1.0
39 msg = "The IPython Notebook requires tornado >= 4.0"
39 msg = "The IPython Notebook requires tornado >= 4.0"
40 try:
40 try:
41 import tornado
41 import tornado
42 except ImportError:
42 except ImportError:
43 raise ImportError(msg)
43 raise ImportError(msg)
44 try:
44 try:
45 version_info = tornado.version_info
45 version_info = tornado.version_info
46 except AttributeError:
46 except AttributeError:
47 raise ImportError(msg + ", but you have < 1.1.0")
47 raise ImportError(msg + ", but you have < 1.1.0")
48 if version_info < (4,0):
48 if version_info < (4,0):
49 raise ImportError(msg + ", but you have %s" % tornado.version)
49 raise ImportError(msg + ", but you have %s" % tornado.version)
50
50
51 from tornado import httpserver
51 from tornado import httpserver
52 from tornado import web
52 from tornado import web
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54
54
55 from IPython.html import (
55 from IPython.html import (
56 DEFAULT_STATIC_FILES_PATH,
56 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_TEMPLATE_PATH_LIST,
57 DEFAULT_TEMPLATE_PATH_LIST,
58 )
58 )
59 from .base.handlers import Template404
59 from .base.handlers import Template404
60 from .log import log_request
60 from .log import log_request
61 from .services.kernels.kernelmanager import MappingKernelManager
61 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.config import ConfigManager
62 from .services.config import ConfigManager
63 from .services.contents.manager import ContentsManager
63 from .services.contents.manager import ContentsManager
64 from .services.contents.filemanager import FileContentsManager
64 from .services.contents.filemanager import FileContentsManager
65 from .services.clusters.clustermanager import ClusterManager
65 from .services.clusters.clustermanager import ClusterManager
66 from .services.sessions.sessionmanager import SessionManager
66 from .services.sessions.sessionmanager import SessionManager
67
67
68 from .auth.login import LoginHandler
68 from .auth.login import LoginHandler
69 from .auth.logout import LogoutHandler
69 from .auth.logout import LogoutHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
71
71
72 from IPython.config import Config
72 from IPython.config import Config
73 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import (
74 from IPython.core.application import (
75 BaseIPythonApplication, base_flags, base_aliases,
75 BaseIPythonApplication, base_flags, base_aliases,
76 )
76 )
77 from IPython.core.profiledir import ProfileDir
77 from IPython.core.profiledir import ProfileDir
78 from IPython.kernel import KernelManager
78 from IPython.kernel import KernelManager
79 from IPython.kernel.kernelspec import KernelSpecManager
79 from IPython.kernel.kernelspec import KernelSpecManager
80 from IPython.kernel.zmq.session import Session
80 from IPython.kernel.zmq.session import Session
81 from IPython.nbformat.sign import NotebookNotary
81 from IPython.nbformat.sign import NotebookNotary
82 from IPython.utils.importstring import import_item
82 from IPython.utils.importstring import import_item
83 from IPython.utils import submodule
83 from IPython.utils import submodule
84 from IPython.utils.process import check_pid
84 from IPython.utils.process import check_pid
85 from IPython.utils.traitlets import (
85 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 TraitError, Type,
87 TraitError, Type,
88 )
88 )
89 from IPython.utils import py3compat
89 from IPython.utils import py3compat
90 from IPython.utils.path import filefind, get_ipython_dir
90 from IPython.utils.path import filefind, get_ipython_dir
91 from IPython.utils.sysinfo import get_sys_info
91 from IPython.utils.sysinfo import get_sys_info
92
92
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 from .utils import url_path_join
94 from .utils import url_path_join
95
95
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 # Module globals
97 # Module globals
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99
99
100 _examples = """
100 _examples = """
101 ipython notebook # start the notebook
101 ipython notebook # start the notebook
102 ipython notebook --profile=sympy # use the sympy profile
102 ipython notebook --profile=sympy # use the sympy profile
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 """
104 """
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Helper functions
107 # Helper functions
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 def random_ports(port, n):
110 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
111 """Generate a list of n random ports near the given port.
112
112
113 The first 5 ports will be sequential, and the remaining n-5 will be
113 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
114 randomly selected in the range [port-2*n, port+2*n].
115 """
115 """
116 for i in range(min(5, n)):
116 for i in range(min(5, n)):
117 yield port + i
117 yield port + i
118 for i in range(n-5):
118 for i in range(n-5):
119 yield max(1, port + random.randint(-2*n, 2*n))
119 yield max(1, port + random.randint(-2*n, 2*n))
120
120
121 def load_handlers(name):
121 def load_handlers(name):
122 """Load the (URL pattern, handler) tuples for each component."""
122 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.html.' + name
123 name = 'IPython.html.' + name
124 mod = __import__(name, fromlist=['default_handlers'])
124 mod = __import__(name, fromlist=['default_handlers'])
125 return mod.default_handlers
125 return mod.default_handlers
126
126
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128 # The Tornado web application
128 # The Tornado web application
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130
130
131 class NotebookWebApplication(web.Application):
131 class NotebookWebApplication(web.Application):
132
132
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
135 config_manager, log,
135 config_manager, log,
136 base_url, default_url, settings_overrides, jinja_env_options):
136 base_url, default_url, settings_overrides, jinja_env_options):
137
137
138 settings = self.init_settings(
138 settings = self.init_settings(
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 default_url, settings_overrides, jinja_env_options)
141 default_url, settings_overrides, jinja_env_options)
142 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
143
143
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
145
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
148 config_manager,
148 config_manager,
149 log, base_url, default_url, settings_overrides,
149 log, base_url, default_url, settings_overrides,
150 jinja_env_options=None):
150 jinja_env_options=None):
151
151
152 _template_path = settings_overrides.get(
152 _template_path = settings_overrides.get(
153 "template_path",
153 "template_path",
154 ipython_app.template_file_path,
154 ipython_app.template_file_path,
155 )
155 )
156 if isinstance(_template_path, str):
156 if isinstance(_template_path, str):
157 _template_path = (_template_path,)
157 _template_path = (_template_path,)
158 template_path = [os.path.expanduser(path) for path in _template_path]
158 template_path = [os.path.expanduser(path) for path in _template_path]
159
159
160 jenv_opt = jinja_env_options if jinja_env_options else {}
160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162
162
163 sys_info = get_sys_info()
163 sys_info = get_sys_info()
164 if sys_info['commit_source'] == 'repository':
164 if sys_info['commit_source'] == 'repository':
165 # don't cache (rely on 304) when working from master
165 # don't cache (rely on 304) when working from master
166 version_hash = ''
166 version_hash = ''
167 else:
167 else:
168 # reset the cache on server restart
168 # reset the cache on server restart
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170
170
171 settings = dict(
171 settings = dict(
172 # basics
172 # basics
173 log_function=log_request,
173 log_function=log_request,
174 base_url=base_url,
174 base_url=base_url,
175 default_url=default_url,
175 default_url=default_url,
176 template_path=template_path,
176 template_path=template_path,
177 static_path=ipython_app.static_file_path,
177 static_path=ipython_app.static_file_path,
178 static_handler_class = FileFindHandler,
178 static_handler_class = FileFindHandler,
179 static_url_prefix = url_path_join(base_url,'/static/'),
179 static_url_prefix = url_path_join(base_url,'/static/'),
180 static_handler_args = {
180 static_handler_args = {
181 # don't cache custom.js
181 # don't cache custom.js
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 },
183 },
184 version_hash=version_hash,
184 version_hash=version_hash,
185
185
186 # authentication
186 # authentication
187 cookie_secret=ipython_app.cookie_secret,
187 cookie_secret=ipython_app.cookie_secret,
188 login_url=url_path_join(base_url,'/login'),
188 login_url=url_path_join(base_url,'/login'),
189 login_handler_class=ipython_app.login_handler_class,
189 login_handler_class=ipython_app.login_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
191 password=ipython_app.password,
191 password=ipython_app.password,
192
192
193 # managers
193 # managers
194 kernel_manager=kernel_manager,
194 kernel_manager=kernel_manager,
195 contents_manager=contents_manager,
195 contents_manager=contents_manager,
196 cluster_manager=cluster_manager,
196 cluster_manager=cluster_manager,
197 session_manager=session_manager,
197 session_manager=session_manager,
198 kernel_spec_manager=kernel_spec_manager,
198 kernel_spec_manager=kernel_spec_manager,
199 config_manager=config_manager,
199 config_manager=config_manager,
200
200
201 # IPython stuff
201 # IPython stuff
202 nbextensions_path=ipython_app.nbextensions_path,
202 nbextensions_path=ipython_app.nbextensions_path,
203 websocket_url=ipython_app.websocket_url,
203 websocket_url=ipython_app.websocket_url,
204 mathjax_url=ipython_app.mathjax_url,
204 mathjax_url=ipython_app.mathjax_url,
205 config=ipython_app.config,
205 config=ipython_app.config,
206 jinja2_env=env,
206 jinja2_env=env,
207 terminals_available=False, # Set later if terminals are available
207 terminals_available=False, # Set later if terminals are available
208 )
208 )
209
209
210 # allow custom overrides for the tornado web app.
210 # allow custom overrides for the tornado web app.
211 settings.update(settings_overrides)
211 settings.update(settings_overrides)
212 return settings
212 return settings
213
213
214 def init_handlers(self, settings):
214 def init_handlers(self, settings):
215 """Load the (URL pattern, handler) tuples for each component."""
215 """Load the (URL pattern, handler) tuples for each component."""
216
216
217 # Order matters. The first handler to match the URL will handle the request.
217 # Order matters. The first handler to match the URL will handle the request.
218 handlers = []
218 handlers = []
219 handlers.extend(load_handlers('tree.handlers'))
219 handlers.extend(load_handlers('tree.handlers'))
220 handlers.extend([(r"/login", settings['login_handler_class'])])
220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 handlers.extend(load_handlers('files.handlers'))
222 handlers.extend(load_handlers('files.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
235 handlers.append(
235 handlers.append(
236 (r"/nbextensions/(.*)", FileFindHandler, {
236 (r"/nbextensions/(.*)", FileFindHandler, {
237 'path': settings['nbextensions_path'],
237 'path': settings['nbextensions_path'],
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 }),
239 }),
240 )
240 )
241 # register base handlers last
241 # register base handlers last
242 handlers.extend(load_handlers('base.handlers'))
242 handlers.extend(load_handlers('base.handlers'))
243 # set the URL that will be redirected from `/`
243 # set the URL that will be redirected from `/`
244 handlers.append(
244 handlers.append(
245 (r'/?', web.RedirectHandler, {
245 (r'/?', web.RedirectHandler, {
246 'url' : settings['default_url'],
246 'url' : settings['default_url'],
247 'permanent': False, # want 302, not 301
247 'permanent': False, # want 302, not 301
248 })
248 })
249 )
249 )
250 # prepend base_url onto the patterns that we match
250 # prepend base_url onto the patterns that we match
251 new_handlers = []
251 new_handlers = []
252 for handler in handlers:
252 for handler in handlers:
253 pattern = url_path_join(settings['base_url'], handler[0])
253 pattern = url_path_join(settings['base_url'], handler[0])
254 new_handler = tuple([pattern] + list(handler[1:]))
254 new_handler = tuple([pattern] + list(handler[1:]))
255 new_handlers.append(new_handler)
255 new_handlers.append(new_handler)
256 # add 404 on the end, which will catch everything that falls through
256 # add 404 on the end, which will catch everything that falls through
257 new_handlers.append((r'(.*)', Template404))
257 new_handlers.append((r'(.*)', Template404))
258 return new_handlers
258 return new_handlers
259
259
260
260
261 class NbserverListApp(BaseIPythonApplication):
261 class NbserverListApp(BaseIPythonApplication):
262
262
263 description="List currently running notebook servers in this profile."
263 description="List currently running notebook servers in this profile."
264
264
265 flags = dict(
265 flags = dict(
266 json=({'NbserverListApp': {'json': True}},
266 json=({'NbserverListApp': {'json': True}},
267 "Produce machine-readable JSON output."),
267 "Produce machine-readable JSON output."),
268 )
268 )
269
269
270 json = Bool(False, config=True,
270 json = Bool(False, config=True,
271 help="If True, each line of output will be a JSON object with the "
271 help="If True, each line of output will be a JSON object with the "
272 "details from the server info file.")
272 "details from the server info file.")
273
273
274 def start(self):
274 def start(self):
275 if not self.json:
275 if not self.json:
276 print("Currently running servers:")
276 print("Currently running servers:")
277 for serverinfo in list_running_servers(self.profile):
277 for serverinfo in list_running_servers(self.profile):
278 if self.json:
278 if self.json:
279 print(json.dumps(serverinfo))
279 print(json.dumps(serverinfo))
280 else:
280 else:
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282
282
283 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
284 # Aliases and Flags
284 # Aliases and Flags
285 #-----------------------------------------------------------------------------
285 #-----------------------------------------------------------------------------
286
286
287 flags = dict(base_flags)
287 flags = dict(base_flags)
288 flags['no-browser']=(
288 flags['no-browser']=(
289 {'NotebookApp' : {'open_browser' : False}},
289 {'NotebookApp' : {'open_browser' : False}},
290 "Don't open the notebook in a browser after startup."
290 "Don't open the notebook in a browser after startup."
291 )
291 )
292 flags['pylab']=(
292 flags['pylab']=(
293 {'NotebookApp' : {'pylab' : 'warn'}},
293 {'NotebookApp' : {'pylab' : 'warn'}},
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 )
295 )
296 flags['no-mathjax']=(
296 flags['no-mathjax']=(
297 {'NotebookApp' : {'enable_mathjax' : False}},
297 {'NotebookApp' : {'enable_mathjax' : False}},
298 """Disable MathJax
298 """Disable MathJax
299
299
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 very large, so you may want to disable it if you have a slow internet
301 very large, so you may want to disable it if you have a slow internet
302 connection, or for offline use of the notebook.
302 connection, or for offline use of the notebook.
303
303
304 When disabled, equations etc. will appear as their untransformed TeX source.
304 When disabled, equations etc. will appear as their untransformed TeX source.
305 """
305 """
306 )
306 )
307
307
308 # Add notebook manager flags
308 # Add notebook manager flags
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 'DEPRECATED, IGNORED',
310 'DEPRECATED, IGNORED',
311 'DEPRECATED, IGNORED'))
311 'DEPRECATED, IGNORED'))
312
312
313 aliases = dict(base_aliases)
313 aliases = dict(base_aliases)
314
314
315 aliases.update({
315 aliases.update({
316 'ip': 'NotebookApp.ip',
316 'ip': 'NotebookApp.ip',
317 'port': 'NotebookApp.port',
317 'port': 'NotebookApp.port',
318 'port-retries': 'NotebookApp.port_retries',
318 'port-retries': 'NotebookApp.port_retries',
319 'transport': 'KernelManager.transport',
319 'transport': 'KernelManager.transport',
320 'keyfile': 'NotebookApp.keyfile',
320 'keyfile': 'NotebookApp.keyfile',
321 'certfile': 'NotebookApp.certfile',
321 'certfile': 'NotebookApp.certfile',
322 'notebook-dir': 'NotebookApp.notebook_dir',
322 'notebook-dir': 'NotebookApp.notebook_dir',
323 'browser': 'NotebookApp.browser',
323 'browser': 'NotebookApp.browser',
324 'pylab': 'NotebookApp.pylab',
324 'pylab': 'NotebookApp.pylab',
325 })
325 })
326
326
327 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
328 # NotebookApp
328 # NotebookApp
329 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
330
330
331 class NotebookApp(BaseIPythonApplication):
331 class NotebookApp(BaseIPythonApplication):
332
332
333 name = 'ipython-notebook'
333 name = 'ipython-notebook'
334
334
335 description = """
335 description = """
336 The IPython HTML Notebook.
336 The IPython HTML Notebook.
337
337
338 This launches a Tornado based HTML Notebook Server that serves up an
338 This launches a Tornado based HTML Notebook Server that serves up an
339 HTML5/Javascript Notebook client.
339 HTML5/Javascript Notebook client.
340 """
340 """
341 examples = _examples
341 examples = _examples
342 aliases = aliases
342 aliases = aliases
343 flags = flags
343 flags = flags
344
344
345 classes = [
345 classes = [
346 KernelManager, ProfileDir, Session, MappingKernelManager,
346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 ContentsManager, FileContentsManager, NotebookNotary,
347 ContentsManager, FileContentsManager, NotebookNotary,
348 KernelSpecManager,
348 KernelSpecManager,
349 ]
349 ]
350 flags = Dict(flags)
350 flags = Dict(flags)
351 aliases = Dict(aliases)
351 aliases = Dict(aliases)
352
352
353 subcommands = dict(
353 subcommands = dict(
354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 )
355 )
356
356
357 _log_formatter_cls = LogFormatter
357 _log_formatter_cls = LogFormatter
358
358
359 def _log_level_default(self):
359 def _log_level_default(self):
360 return logging.INFO
360 return logging.INFO
361
361
362 def _log_datefmt_default(self):
362 def _log_datefmt_default(self):
363 """Exclude date from default date format"""
363 """Exclude date from default date format"""
364 return "%H:%M:%S"
364 return "%H:%M:%S"
365
365
366 def _log_format_default(self):
366 def _log_format_default(self):
367 """override default log format to include time"""
367 """override default log format to include time"""
368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369
369
370 # create requested profiles by default, if they don't exist:
370 # create requested profiles by default, if they don't exist:
371 auto_create = Bool(True)
371 auto_create = Bool(True)
372
372
373 # file to be opened in the notebook server
373 # file to be opened in the notebook server
374 file_to_run = Unicode('', config=True)
374 file_to_run = Unicode('', config=True)
375
375
376 # Network related information
376 # Network related information
377
377
378 allow_origin = Unicode('', config=True,
378 allow_origin = Unicode('', config=True,
379 help="""Set the Access-Control-Allow-Origin header
379 help="""Set the Access-Control-Allow-Origin header
380
380
381 Use '*' to allow any origin to access your server.
381 Use '*' to allow any origin to access your server.
382
382
383 Takes precedence over allow_origin_pat.
383 Takes precedence over allow_origin_pat.
384 """
384 """
385 )
385 )
386
386
387 allow_origin_pat = Unicode('', config=True,
387 allow_origin_pat = Unicode('', config=True,
388 help="""Use a regular expression for the Access-Control-Allow-Origin header
388 help="""Use a regular expression for the Access-Control-Allow-Origin header
389
389
390 Requests from an origin matching the expression will get replies with:
390 Requests from an origin matching the expression will get replies with:
391
391
392 Access-Control-Allow-Origin: origin
392 Access-Control-Allow-Origin: origin
393
393
394 where `origin` is the origin of the request.
394 where `origin` is the origin of the request.
395
395
396 Ignored if allow_origin is set.
396 Ignored if allow_origin is set.
397 """
397 """
398 )
398 )
399
399
400 allow_credentials = Bool(False, config=True,
400 allow_credentials = Bool(False, config=True,
401 help="Set the Access-Control-Allow-Credentials: true header"
401 help="Set the Access-Control-Allow-Credentials: true header"
402 )
402 )
403
403
404 default_url = Unicode('/tree', config=True,
404 default_url = Unicode('/tree', config=True,
405 help="The default URL to redirect to from `/`"
405 help="The default URL to redirect to from `/`"
406 )
406 )
407
407
408 ip = Unicode('localhost', config=True,
408 ip = Unicode('localhost', config=True,
409 help="The IP address the notebook server will listen on."
409 help="The IP address the notebook server will listen on."
410 )
410 )
411 def _ip_default(self):
411 def _ip_default(self):
412 """Return localhost if available, 127.0.0.1 otherwise.
412 """Return localhost if available, 127.0.0.1 otherwise.
413
413
414 On some (horribly broken) systems, localhost cannot be bound.
414 On some (horribly broken) systems, localhost cannot be bound.
415 """
415 """
416 s = socket.socket()
416 s = socket.socket()
417 try:
417 try:
418 s.bind(('localhost', 0))
418 s.bind(('localhost', 0))
419 except socket.error as e:
419 except socket.error as e:
420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
421 return '127.0.0.1'
421 return '127.0.0.1'
422 else:
422 else:
423 s.close()
423 s.close()
424 return 'localhost'
424 return 'localhost'
425
425
426 def _ip_changed(self, name, old, new):
426 def _ip_changed(self, name, old, new):
427 if new == u'*': self.ip = u''
427 if new == u'*': self.ip = u''
428
428
429 port = Integer(8888, config=True,
429 port = Integer(8888, config=True,
430 help="The port the notebook server will listen on."
430 help="The port the notebook server will listen on."
431 )
431 )
432 port_retries = Integer(50, config=True,
432 port_retries = Integer(50, config=True,
433 help="The number of additional ports to try if the specified port is not available."
433 help="The number of additional ports to try if the specified port is not available."
434 )
434 )
435
435
436 certfile = Unicode(u'', config=True,
436 certfile = Unicode(u'', config=True,
437 help="""The full path to an SSL/TLS certificate file."""
437 help="""The full path to an SSL/TLS certificate file."""
438 )
438 )
439
439
440 keyfile = Unicode(u'', config=True,
440 keyfile = Unicode(u'', config=True,
441 help="""The full path to a private key file for usage with SSL/TLS."""
441 help="""The full path to a private key file for usage with SSL/TLS."""
442 )
442 )
443
443
444 cookie_secret_file = Unicode(config=True,
444 cookie_secret_file = Unicode(config=True,
445 help="""The file where the cookie secret is stored."""
445 help="""The file where the cookie secret is stored."""
446 )
446 )
447 def _cookie_secret_file_default(self):
447 def _cookie_secret_file_default(self):
448 if self.profile_dir is None:
448 if self.profile_dir is None:
449 return ''
449 return ''
450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
451
451
452 cookie_secret = Bytes(b'', config=True,
452 cookie_secret = Bytes(b'', config=True,
453 help="""The random bytes used to secure cookies.
453 help="""The random bytes used to secure cookies.
454 By default this is a new random number every time you start the Notebook.
454 By default this is a new random number every time you start the Notebook.
455 Set it to a value in a config file to enable logins to persist across server sessions.
455 Set it to a value in a config file to enable logins to persist across server sessions.
456
456
457 Note: Cookie secrets should be kept private, do not share config files with
457 Note: Cookie secrets should be kept private, do not share config files with
458 cookie_secret stored in plaintext (you can read the value from a file).
458 cookie_secret stored in plaintext (you can read the value from a file).
459 """
459 """
460 )
460 )
461 def _cookie_secret_default(self):
461 def _cookie_secret_default(self):
462 if os.path.exists(self.cookie_secret_file):
462 if os.path.exists(self.cookie_secret_file):
463 with io.open(self.cookie_secret_file, 'rb') as f:
463 with io.open(self.cookie_secret_file, 'rb') as f:
464 return f.read()
464 return f.read()
465 else:
465 else:
466 secret = base64.encodestring(os.urandom(1024))
466 secret = base64.encodestring(os.urandom(1024))
467 self._write_cookie_secret_file(secret)
467 self._write_cookie_secret_file(secret)
468 return secret
468 return secret
469
469
470 def _write_cookie_secret_file(self, secret):
470 def _write_cookie_secret_file(self, secret):
471 """write my secret to my secret_file"""
471 """write my secret to my secret_file"""
472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
473 with io.open(self.cookie_secret_file, 'wb') as f:
473 with io.open(self.cookie_secret_file, 'wb') as f:
474 f.write(secret)
474 f.write(secret)
475 try:
475 try:
476 os.chmod(self.cookie_secret_file, 0o600)
476 os.chmod(self.cookie_secret_file, 0o600)
477 except OSError:
477 except OSError:
478 self.log.warn(
478 self.log.warn(
479 "Could not set permissions on %s",
479 "Could not set permissions on %s",
480 self.cookie_secret_file
480 self.cookie_secret_file
481 )
481 )
482
482
483 password = Unicode(u'', config=True,
483 password = Unicode(u'', config=True,
484 help="""Hashed password to use for web authentication.
484 help="""Hashed password to use for web authentication.
485
485
486 To generate, type in a python/IPython shell:
486 To generate, type in a python/IPython shell:
487
487
488 from IPython.lib import passwd; passwd()
488 from IPython.lib import passwd; passwd()
489
489
490 The string should be of the form type:salt:hashed-password.
490 The string should be of the form type:salt:hashed-password.
491 """
491 """
492 )
492 )
493
493
494 open_browser = Bool(True, config=True,
494 open_browser = Bool(True, config=True,
495 help="""Whether to open in a browser after starting.
495 help="""Whether to open in a browser after starting.
496 The specific browser used is platform dependent and
496 The specific browser used is platform dependent and
497 determined by the python standard library `webbrowser`
497 determined by the python standard library `webbrowser`
498 module, unless it is overridden using the --browser
498 module, unless it is overridden using the --browser
499 (NotebookApp.browser) configuration option.
499 (NotebookApp.browser) configuration option.
500 """)
500 """)
501
501
502 browser = Unicode(u'', config=True,
502 browser = Unicode(u'', config=True,
503 help="""Specify what command to use to invoke a web
503 help="""Specify what command to use to invoke a web
504 browser when opening the notebook. If not specified, the
504 browser when opening the notebook. If not specified, the
505 default browser will be determined by the `webbrowser`
505 default browser will be determined by the `webbrowser`
506 standard library module, which allows setting of the
506 standard library module, which allows setting of the
507 BROWSER environment variable to override it.
507 BROWSER environment variable to override it.
508 """)
508 """)
509
509
510 webapp_settings = Dict(config=True,
510 webapp_settings = Dict(config=True,
511 help="DEPRECATED, use tornado_settings"
511 help="DEPRECATED, use tornado_settings"
512 )
512 )
513 def _webapp_settings_changed(self, name, old, new):
513 def _webapp_settings_changed(self, name, old, new):
514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
515 self.tornado_settings = new
515 self.tornado_settings = new
516
516
517 tornado_settings = Dict(config=True,
517 tornado_settings = Dict(config=True,
518 help="Supply overrides for the tornado.web.Application that the "
518 help="Supply overrides for the tornado.web.Application that the "
519 "IPython notebook uses.")
519 "IPython notebook uses.")
520
520
521 ssl_options = Dict(config=True,
521 ssl_options = Dict(config=True,
522 help="""Supply SSL options for the tornado HTTPServer.
522 help="""Supply SSL options for the tornado HTTPServer.
523 See the tornado docs for details.""")
523 See the tornado docs for details.""")
524
524
525 jinja_environment_options = Dict(config=True,
525 jinja_environment_options = Dict(config=True,
526 help="Supply extra arguments that will be passed to Jinja environment.")
526 help="Supply extra arguments that will be passed to Jinja environment.")
527
527
528 enable_mathjax = Bool(True, config=True,
528 enable_mathjax = Bool(True, config=True,
529 help="""Whether to enable MathJax for typesetting math/TeX
529 help="""Whether to enable MathJax for typesetting math/TeX
530
530
531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
532 very large, so you may want to disable it if you have a slow internet
532 very large, so you may want to disable it if you have a slow internet
533 connection, or for offline use of the notebook.
533 connection, or for offline use of the notebook.
534
534
535 When disabled, equations etc. will appear as their untransformed TeX source.
535 When disabled, equations etc. will appear as their untransformed TeX source.
536 """
536 """
537 )
537 )
538 def _enable_mathjax_changed(self, name, old, new):
538 def _enable_mathjax_changed(self, name, old, new):
539 """set mathjax url to empty if mathjax is disabled"""
539 """set mathjax url to empty if mathjax is disabled"""
540 if not new:
540 if not new:
541 self.mathjax_url = u''
541 self.mathjax_url = u''
542
542
543 base_url = Unicode('/', config=True,
543 base_url = Unicode('/', config=True,
544 help='''The base URL for the notebook server.
544 help='''The base URL for the notebook server.
545
545
546 Leading and trailing slashes can be omitted,
546 Leading and trailing slashes can be omitted,
547 and will automatically be added.
547 and will automatically be added.
548 ''')
548 ''')
549 def _base_url_changed(self, name, old, new):
549 def _base_url_changed(self, name, old, new):
550 if not new.startswith('/'):
550 if not new.startswith('/'):
551 self.base_url = '/'+new
551 self.base_url = '/'+new
552 elif not new.endswith('/'):
552 elif not new.endswith('/'):
553 self.base_url = new+'/'
553 self.base_url = new+'/'
554
554
555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
556 def _base_project_url_changed(self, name, old, new):
556 def _base_project_url_changed(self, name, old, new):
557 self.log.warn("base_project_url is deprecated, use base_url")
557 self.log.warn("base_project_url is deprecated, use base_url")
558 self.base_url = new
558 self.base_url = new
559
559
560 extra_static_paths = List(Unicode, config=True,
560 extra_static_paths = List(Unicode, config=True,
561 help="""Extra paths to search for serving static files.
561 help="""Extra paths to search for serving static files.
562
562
563 This allows adding javascript/css to be available from the notebook server machine,
563 This allows adding javascript/css to be available from the notebook server machine,
564 or overriding individual files in the IPython"""
564 or overriding individual files in the IPython"""
565 )
565 )
566 def _extra_static_paths_default(self):
566 def _extra_static_paths_default(self):
567 return [os.path.join(self.profile_dir.location, 'static')]
567 return [os.path.join(self.profile_dir.location, 'static')]
568
568
569 @property
569 @property
570 def static_file_path(self):
570 def static_file_path(self):
571 """return extra paths + the default location"""
571 """return extra paths + the default location"""
572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
573
573
574 extra_template_paths = List(Unicode, config=True,
574 extra_template_paths = List(Unicode, config=True,
575 help="""Extra paths to search for serving jinja templates.
575 help="""Extra paths to search for serving jinja templates.
576
576
577 Can be used to override templates from IPython.html.templates."""
577 Can be used to override templates from IPython.html.templates."""
578 )
578 )
579 def _extra_template_paths_default(self):
579 def _extra_template_paths_default(self):
580 return []
580 return []
581
581
582 @property
582 @property
583 def template_file_path(self):
583 def template_file_path(self):
584 """return extra paths + the default locations"""
584 """return extra paths + the default locations"""
585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
586
586
587 extra_nbextensions_path = List(Unicode, config=True,
587 extra_nbextensions_path = List(Unicode, config=True,
588 help="""extra paths to look for Javascript notebook extensions"""
588 help="""extra paths to look for Javascript notebook extensions"""
589 )
589 )
590
590
591 @property
591 @property
592 def nbextensions_path(self):
592 def nbextensions_path(self):
593 """The path to look for Javascript notebook extensions"""
593 """The path to look for Javascript notebook extensions"""
594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
595
595
596 websocket_url = Unicode("", config=True,
596 websocket_url = Unicode("", config=True,
597 help="""The base URL for websockets,
597 help="""The base URL for websockets,
598 if it differs from the HTTP server (hint: it almost certainly doesn't).
598 if it differs from the HTTP server (hint: it almost certainly doesn't).
599
599
600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
601 """
601 """
602 )
602 )
603 mathjax_url = Unicode("", config=True,
603 mathjax_url = Unicode("", config=True,
604 help="""The url for MathJax.js."""
604 help="""The url for MathJax.js."""
605 )
605 )
606 def _mathjax_url_default(self):
606 def _mathjax_url_default(self):
607 if not self.enable_mathjax:
607 if not self.enable_mathjax:
608 return u''
608 return u''
609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
610 url_path_join(self.base_url, "static")
610 url_path_join(self.base_url, "static")
611 )
611 )
612
612
613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
614 for (url_prefix, search_path) in [
614 for (url_prefix, search_path) in [
615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
616 (static_url_prefix, self.static_file_path),
616 (static_url_prefix, self.static_file_path),
617 ]:
617 ]:
618 self.log.debug("searching for local mathjax in %s", search_path)
618 self.log.debug("searching for local mathjax in %s", search_path)
619 try:
619 try:
620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
621 except IOError:
621 except IOError:
622 continue
622 continue
623 else:
623 else:
624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
626 return url
626 return url
627
627
628 # no local mathjax, serve from CDN
628 # no local mathjax, serve from CDN
629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
630 self.log.info("Using MathJax from CDN: %s", url)
630 self.log.info("Using MathJax from CDN: %s", url)
631 return url
631 return url
632
632
633 def _mathjax_url_changed(self, name, old, new):
633 def _mathjax_url_changed(self, name, old, new):
634 if new and not self.enable_mathjax:
634 if new and not self.enable_mathjax:
635 # enable_mathjax=False overrides mathjax_url
635 # enable_mathjax=False overrides mathjax_url
636 self.mathjax_url = u''
636 self.mathjax_url = u''
637 else:
637 else:
638 self.log.info("Using MathJax: %s", new)
638 self.log.info("Using MathJax: %s", new)
639
639
640 contents_manager_class = Type(
640 contents_manager_class = Type(
641 default_value=FileContentsManager,
641 default_value=FileContentsManager,
642 klass=ContentsManager,
642 klass=ContentsManager,
643 config=True,
643 config=True,
644 help='The notebook manager class to use.'
644 help='The notebook manager class to use.'
645 )
645 )
646 kernel_manager_class = Type(
646 kernel_manager_class = Type(
647 default_value=MappingKernelManager,
647 default_value=MappingKernelManager,
648 config=True,
648 config=True,
649 help='The kernel manager class to use.'
649 help='The kernel manager class to use.'
650 )
650 )
651 session_manager_class = Type(
651 session_manager_class = Type(
652 default_value=SessionManager,
652 default_value=SessionManager,
653 config=True,
653 config=True,
654 help='The session manager class to use.'
654 help='The session manager class to use.'
655 )
655 )
656 cluster_manager_class = Type(
656 cluster_manager_class = Type(
657 default_value=ClusterManager,
657 default_value=ClusterManager,
658 config=True,
658 config=True,
659 help='The cluster manager class to use.'
659 help='The cluster manager class to use.'
660 )
660 )
661
661
662 config_manager_class = Type(
662 config_manager_class = Type(
663 default_value=ConfigManager,
663 default_value=ConfigManager,
664 config = True,
664 config = True,
665 help='The config manager class to use'
665 help='The config manager class to use'
666 )
666 )
667
667
668 kernel_spec_manager = Instance(KernelSpecManager)
668 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
669
669
670 kernel_spec_manager_class = Type(
670 kernel_spec_manager_class = Type(
671 default_value=KernelSpecManager,
671 default_value=KernelSpecManager,
672 config=True,
672 config=True,
673 help="""
673 help="""
674 The kernel spec manager class to use. Should be a subclass
674 The kernel spec manager class to use. Should be a subclass
675 of `IPython.kernel.kernelspec.KernelSpecManager`.
675 of `IPython.kernel.kernelspec.KernelSpecManager`.
676
676
677 The Api of KernelSpecManager is provisional and might change
677 The Api of KernelSpecManager is provisional and might change
678 without warning between this version of IPython and the next stable one.
678 without warning between this version of IPython and the next stable one.
679 """
679 """
680 )
680 )
681
681
682 login_handler_class = Type(
682 login_handler_class = Type(
683 default_value=LoginHandler,
683 default_value=LoginHandler,
684 klass=web.RequestHandler,
684 klass=web.RequestHandler,
685 config=True,
685 config=True,
686 help='The login handler class to use.',
686 help='The login handler class to use.',
687 )
687 )
688
688
689 logout_handler_class = Type(
689 logout_handler_class = Type(
690 default_value=LogoutHandler,
690 default_value=LogoutHandler,
691 klass=web.RequestHandler,
691 klass=web.RequestHandler,
692 config=True,
692 config=True,
693 help='The logout handler class to use.',
693 help='The logout handler class to use.',
694 )
694 )
695
695
696 trust_xheaders = Bool(False, config=True,
696 trust_xheaders = Bool(False, config=True,
697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
699 )
699 )
700
700
701 info_file = Unicode()
701 info_file = Unicode()
702
702
703 def _info_file_default(self):
703 def _info_file_default(self):
704 info_file = "nbserver-%s.json"%os.getpid()
704 info_file = "nbserver-%s.json"%os.getpid()
705 return os.path.join(self.profile_dir.security_dir, info_file)
705 return os.path.join(self.profile_dir.security_dir, info_file)
706
706
707 pylab = Unicode('disabled', config=True,
707 pylab = Unicode('disabled', config=True,
708 help="""
708 help="""
709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
710 """
710 """
711 )
711 )
712 def _pylab_changed(self, name, old, new):
712 def _pylab_changed(self, name, old, new):
713 """when --pylab is specified, display a warning and exit"""
713 """when --pylab is specified, display a warning and exit"""
714 if new != 'warn':
714 if new != 'warn':
715 backend = ' %s' % new
715 backend = ' %s' % new
716 else:
716 else:
717 backend = ''
717 backend = ''
718 self.log.error("Support for specifying --pylab on the command line has been removed.")
718 self.log.error("Support for specifying --pylab on the command line has been removed.")
719 self.log.error(
719 self.log.error(
720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
721 )
721 )
722 self.exit(1)
722 self.exit(1)
723
723
724 notebook_dir = Unicode(config=True,
724 notebook_dir = Unicode(config=True,
725 help="The directory to use for notebooks and kernels."
725 help="The directory to use for notebooks and kernels."
726 )
726 )
727
727
728 def _notebook_dir_default(self):
728 def _notebook_dir_default(self):
729 if self.file_to_run:
729 if self.file_to_run:
730 return os.path.dirname(os.path.abspath(self.file_to_run))
730 return os.path.dirname(os.path.abspath(self.file_to_run))
731 else:
731 else:
732 return py3compat.getcwd()
732 return py3compat.getcwd()
733
733
734 def _notebook_dir_changed(self, name, old, new):
734 def _notebook_dir_changed(self, name, old, new):
735 """Do a bit of validation of the notebook dir."""
735 """Do a bit of validation of the notebook dir."""
736 if not os.path.isabs(new):
736 if not os.path.isabs(new):
737 # If we receive a non-absolute path, make it absolute.
737 # If we receive a non-absolute path, make it absolute.
738 self.notebook_dir = os.path.abspath(new)
738 self.notebook_dir = os.path.abspath(new)
739 return
739 return
740 if not os.path.isdir(new):
740 if not os.path.isdir(new):
741 raise TraitError("No such notebook dir: %r" % new)
741 raise TraitError("No such notebook dir: %r" % new)
742
742
743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
744 self.config.FileContentsManager.root_dir = new
744 self.config.FileContentsManager.root_dir = new
745 self.config.MappingKernelManager.root_dir = new
745 self.config.MappingKernelManager.root_dir = new
746
746
747 server_extensions = List(Unicode(), config=True,
747 server_extensions = List(Unicode(), config=True,
748 help=("Python modules to load as notebook server extensions. "
748 help=("Python modules to load as notebook server extensions. "
749 "This is an experimental API, and may change in future releases.")
749 "This is an experimental API, and may change in future releases.")
750 )
750 )
751
751
752 reraise_server_extension_failures = Bool(
752 reraise_server_extension_failures = Bool(
753 False,
753 False,
754 config=True,
754 config=True,
755 help="Reraise exceptions encountered loading server extensions?",
755 help="Reraise exceptions encountered loading server extensions?",
756 )
756 )
757
757
758 def parse_command_line(self, argv=None):
758 def parse_command_line(self, argv=None):
759 super(NotebookApp, self).parse_command_line(argv)
759 super(NotebookApp, self).parse_command_line(argv)
760
760
761 if self.extra_args:
761 if self.extra_args:
762 arg0 = self.extra_args[0]
762 arg0 = self.extra_args[0]
763 f = os.path.abspath(arg0)
763 f = os.path.abspath(arg0)
764 self.argv.remove(arg0)
764 self.argv.remove(arg0)
765 if not os.path.exists(f):
765 if not os.path.exists(f):
766 self.log.critical("No such file or directory: %s", f)
766 self.log.critical("No such file or directory: %s", f)
767 self.exit(1)
767 self.exit(1)
768
768
769 # Use config here, to ensure that it takes higher priority than
769 # Use config here, to ensure that it takes higher priority than
770 # anything that comes from the profile.
770 # anything that comes from the profile.
771 c = Config()
771 c = Config()
772 if os.path.isdir(f):
772 if os.path.isdir(f):
773 c.NotebookApp.notebook_dir = f
773 c.NotebookApp.notebook_dir = f
774 elif os.path.isfile(f):
774 elif os.path.isfile(f):
775 c.NotebookApp.file_to_run = f
775 c.NotebookApp.file_to_run = f
776 self.update_config(c)
776 self.update_config(c)
777
777
778 def init_configurables(self):
778 def init_configurables(self):
779 self.kernel_spec_manager = self.kernel_spec_manager_class(
779 self.kernel_spec_manager = self.kernel_spec_manager_class(
780 parent=self,
780 parent=self,
781 ipython_dir=self.ipython_dir,
781 ipython_dir=self.ipython_dir,
782 )
782 )
783 self.kernel_manager = self.kernel_manager_class(
783 self.kernel_manager = self.kernel_manager_class(
784 parent=self,
784 parent=self,
785 log=self.log,
785 log=self.log,
786 connection_dir=self.profile_dir.security_dir,
786 connection_dir=self.profile_dir.security_dir,
787 )
787 )
788 self.contents_manager = self.contents_manager_class(
788 self.contents_manager = self.contents_manager_class(
789 parent=self,
789 parent=self,
790 log=self.log,
790 log=self.log,
791 )
791 )
792 self.session_manager = self.session_manager_class(
792 self.session_manager = self.session_manager_class(
793 parent=self,
793 parent=self,
794 log=self.log,
794 log=self.log,
795 kernel_manager=self.kernel_manager,
795 kernel_manager=self.kernel_manager,
796 contents_manager=self.contents_manager,
796 contents_manager=self.contents_manager,
797 )
797 )
798 self.cluster_manager = self.cluster_manager_class(
798 self.cluster_manager = self.cluster_manager_class(
799 parent=self,
799 parent=self,
800 log=self.log,
800 log=self.log,
801 )
801 )
802
802
803 self.config_manager = self.config_manager_class(
803 self.config_manager = self.config_manager_class(
804 parent=self,
804 parent=self,
805 log=self.log,
805 log=self.log,
806 profile_dir=self.profile_dir.location,
806 profile_dir=self.profile_dir.location,
807 )
807 )
808
808
809 def init_logging(self):
809 def init_logging(self):
810 # This prevents double log messages because tornado use a root logger that
810 # This prevents double log messages because tornado use a root logger that
811 # self.log is a child of. The logging module dipatches log messages to a log
811 # self.log is a child of. The logging module dipatches log messages to a log
812 # and all of its ancenstors until propagate is set to False.
812 # and all of its ancenstors until propagate is set to False.
813 self.log.propagate = False
813 self.log.propagate = False
814
814
815 for log in app_log, access_log, gen_log:
815 for log in app_log, access_log, gen_log:
816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
817 log.name = self.log.name
817 log.name = self.log.name
818 # hook up tornado 3's loggers to our app handlers
818 # hook up tornado 3's loggers to our app handlers
819 logger = logging.getLogger('tornado')
819 logger = logging.getLogger('tornado')
820 logger.propagate = True
820 logger.propagate = True
821 logger.parent = self.log
821 logger.parent = self.log
822 logger.setLevel(self.log.level)
822 logger.setLevel(self.log.level)
823
823
824 def init_webapp(self):
824 def init_webapp(self):
825 """initialize tornado webapp and httpserver"""
825 """initialize tornado webapp and httpserver"""
826 self.tornado_settings['allow_origin'] = self.allow_origin
826 self.tornado_settings['allow_origin'] = self.allow_origin
827 if self.allow_origin_pat:
827 if self.allow_origin_pat:
828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
829 self.tornado_settings['allow_credentials'] = self.allow_credentials
829 self.tornado_settings['allow_credentials'] = self.allow_credentials
830 # ensure default_url starts with base_url
830 # ensure default_url starts with base_url
831 if not self.default_url.startswith(self.base_url):
831 if not self.default_url.startswith(self.base_url):
832 self.default_url = url_path_join(self.base_url, self.default_url)
832 self.default_url = url_path_join(self.base_url, self.default_url)
833
833
834 self.web_app = NotebookWebApplication(
834 self.web_app = NotebookWebApplication(
835 self, self.kernel_manager, self.contents_manager,
835 self, self.kernel_manager, self.contents_manager,
836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
837 self.config_manager,
837 self.config_manager,
838 self.log, self.base_url, self.default_url, self.tornado_settings,
838 self.log, self.base_url, self.default_url, self.tornado_settings,
839 self.jinja_environment_options
839 self.jinja_environment_options
840 )
840 )
841 ssl_options = self.ssl_options
841 ssl_options = self.ssl_options
842 if self.certfile:
842 if self.certfile:
843 ssl_options['certfile'] = self.certfile
843 ssl_options['certfile'] = self.certfile
844 if self.keyfile:
844 if self.keyfile:
845 ssl_options['keyfile'] = self.keyfile
845 ssl_options['keyfile'] = self.keyfile
846 if not ssl_options:
846 if not ssl_options:
847 # None indicates no SSL config
847 # None indicates no SSL config
848 ssl_options = None
848 ssl_options = None
849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
851 xheaders=self.trust_xheaders)
851 xheaders=self.trust_xheaders)
852
852
853 success = None
853 success = None
854 for port in random_ports(self.port, self.port_retries+1):
854 for port in random_ports(self.port, self.port_retries+1):
855 try:
855 try:
856 self.http_server.listen(port, self.ip)
856 self.http_server.listen(port, self.ip)
857 except socket.error as e:
857 except socket.error as e:
858 if e.errno == errno.EADDRINUSE:
858 if e.errno == errno.EADDRINUSE:
859 self.log.info('The port %i is already in use, trying another random port.' % port)
859 self.log.info('The port %i is already in use, trying another random port.' % port)
860 continue
860 continue
861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
862 self.log.warn("Permission to listen on port %i denied" % port)
862 self.log.warn("Permission to listen on port %i denied" % port)
863 continue
863 continue
864 else:
864 else:
865 raise
865 raise
866 else:
866 else:
867 self.port = port
867 self.port = port
868 success = True
868 success = True
869 break
869 break
870 if not success:
870 if not success:
871 self.log.critical('ERROR: the notebook server could not be started because '
871 self.log.critical('ERROR: the notebook server could not be started because '
872 'no available port could be found.')
872 'no available port could be found.')
873 self.exit(1)
873 self.exit(1)
874
874
875 @property
875 @property
876 def display_url(self):
876 def display_url(self):
877 ip = self.ip if self.ip else '[all ip addresses on your system]'
877 ip = self.ip if self.ip else '[all ip addresses on your system]'
878 return self._url(ip)
878 return self._url(ip)
879
879
880 @property
880 @property
881 def connection_url(self):
881 def connection_url(self):
882 ip = self.ip if self.ip else 'localhost'
882 ip = self.ip if self.ip else 'localhost'
883 return self._url(ip)
883 return self._url(ip)
884
884
885 def _url(self, ip):
885 def _url(self, ip):
886 proto = 'https' if self.certfile else 'http'
886 proto = 'https' if self.certfile else 'http'
887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
888
888
889 def init_terminals(self):
889 def init_terminals(self):
890 try:
890 try:
891 from .terminal import initialize
891 from .terminal import initialize
892 initialize(self.web_app)
892 initialize(self.web_app)
893 self.web_app.settings['terminals_available'] = True
893 self.web_app.settings['terminals_available'] = True
894 except ImportError as e:
894 except ImportError as e:
895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
896 log("Terminals not available (error was %s)", e)
896 log("Terminals not available (error was %s)", e)
897
897
898 def init_signal(self):
898 def init_signal(self):
899 if not sys.platform.startswith('win'):
899 if not sys.platform.startswith('win'):
900 signal.signal(signal.SIGINT, self._handle_sigint)
900 signal.signal(signal.SIGINT, self._handle_sigint)
901 signal.signal(signal.SIGTERM, self._signal_stop)
901 signal.signal(signal.SIGTERM, self._signal_stop)
902 if hasattr(signal, 'SIGUSR1'):
902 if hasattr(signal, 'SIGUSR1'):
903 # Windows doesn't support SIGUSR1
903 # Windows doesn't support SIGUSR1
904 signal.signal(signal.SIGUSR1, self._signal_info)
904 signal.signal(signal.SIGUSR1, self._signal_info)
905 if hasattr(signal, 'SIGINFO'):
905 if hasattr(signal, 'SIGINFO'):
906 # only on BSD-based systems
906 # only on BSD-based systems
907 signal.signal(signal.SIGINFO, self._signal_info)
907 signal.signal(signal.SIGINFO, self._signal_info)
908
908
909 def _handle_sigint(self, sig, frame):
909 def _handle_sigint(self, sig, frame):
910 """SIGINT handler spawns confirmation dialog"""
910 """SIGINT handler spawns confirmation dialog"""
911 # register more forceful signal handler for ^C^C case
911 # register more forceful signal handler for ^C^C case
912 signal.signal(signal.SIGINT, self._signal_stop)
912 signal.signal(signal.SIGINT, self._signal_stop)
913 # request confirmation dialog in bg thread, to avoid
913 # request confirmation dialog in bg thread, to avoid
914 # blocking the App
914 # blocking the App
915 thread = threading.Thread(target=self._confirm_exit)
915 thread = threading.Thread(target=self._confirm_exit)
916 thread.daemon = True
916 thread.daemon = True
917 thread.start()
917 thread.start()
918
918
919 def _restore_sigint_handler(self):
919 def _restore_sigint_handler(self):
920 """callback for restoring original SIGINT handler"""
920 """callback for restoring original SIGINT handler"""
921 signal.signal(signal.SIGINT, self._handle_sigint)
921 signal.signal(signal.SIGINT, self._handle_sigint)
922
922
923 def _confirm_exit(self):
923 def _confirm_exit(self):
924 """confirm shutdown on ^C
924 """confirm shutdown on ^C
925
925
926 A second ^C, or answering 'y' within 5s will cause shutdown,
926 A second ^C, or answering 'y' within 5s will cause shutdown,
927 otherwise original SIGINT handler will be restored.
927 otherwise original SIGINT handler will be restored.
928
928
929 This doesn't work on Windows.
929 This doesn't work on Windows.
930 """
930 """
931 info = self.log.info
931 info = self.log.info
932 info('interrupted')
932 info('interrupted')
933 print(self.notebook_info())
933 print(self.notebook_info())
934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
935 sys.stdout.flush()
935 sys.stdout.flush()
936 r,w,x = select.select([sys.stdin], [], [], 5)
936 r,w,x = select.select([sys.stdin], [], [], 5)
937 if r:
937 if r:
938 line = sys.stdin.readline()
938 line = sys.stdin.readline()
939 if line.lower().startswith('y') and 'n' not in line.lower():
939 if line.lower().startswith('y') and 'n' not in line.lower():
940 self.log.critical("Shutdown confirmed")
940 self.log.critical("Shutdown confirmed")
941 ioloop.IOLoop.current().stop()
941 ioloop.IOLoop.current().stop()
942 return
942 return
943 else:
943 else:
944 print("No answer for 5s:", end=' ')
944 print("No answer for 5s:", end=' ')
945 print("resuming operation...")
945 print("resuming operation...")
946 # no answer, or answer is no:
946 # no answer, or answer is no:
947 # set it back to original SIGINT handler
947 # set it back to original SIGINT handler
948 # use IOLoop.add_callback because signal.signal must be called
948 # use IOLoop.add_callback because signal.signal must be called
949 # from main thread
949 # from main thread
950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
951
951
952 def _signal_stop(self, sig, frame):
952 def _signal_stop(self, sig, frame):
953 self.log.critical("received signal %s, stopping", sig)
953 self.log.critical("received signal %s, stopping", sig)
954 ioloop.IOLoop.current().stop()
954 ioloop.IOLoop.current().stop()
955
955
956 def _signal_info(self, sig, frame):
956 def _signal_info(self, sig, frame):
957 print(self.notebook_info())
957 print(self.notebook_info())
958
958
959 def init_components(self):
959 def init_components(self):
960 """Check the components submodule, and warn if it's unclean"""
960 """Check the components submodule, and warn if it's unclean"""
961 status = submodule.check_submodule_status()
961 status = submodule.check_submodule_status()
962 if status == 'missing':
962 if status == 'missing':
963 self.log.warn("components submodule missing, running `git submodule update`")
963 self.log.warn("components submodule missing, running `git submodule update`")
964 submodule.update_submodules(submodule.ipython_parent())
964 submodule.update_submodules(submodule.ipython_parent())
965 elif status == 'unclean':
965 elif status == 'unclean':
966 self.log.warn("components submodule unclean, you may see 404s on static/components")
966 self.log.warn("components submodule unclean, you may see 404s on static/components")
967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
968
968
969 def init_server_extensions(self):
969 def init_server_extensions(self):
970 """Load any extensions specified by config.
970 """Load any extensions specified by config.
971
971
972 Import the module, then call the load_jupyter_server_extension function,
972 Import the module, then call the load_jupyter_server_extension function,
973 if one exists.
973 if one exists.
974
974
975 The extension API is experimental, and may change in future releases.
975 The extension API is experimental, and may change in future releases.
976 """
976 """
977 for modulename in self.server_extensions:
977 for modulename in self.server_extensions:
978 try:
978 try:
979 mod = importlib.import_module(modulename)
979 mod = importlib.import_module(modulename)
980 func = getattr(mod, 'load_jupyter_server_extension', None)
980 func = getattr(mod, 'load_jupyter_server_extension', None)
981 if func is not None:
981 if func is not None:
982 func(self)
982 func(self)
983 except Exception:
983 except Exception:
984 if self.reraise_server_extension_failures:
984 if self.reraise_server_extension_failures:
985 raise
985 raise
986 self.log.warn("Error loading server extension %s", modulename,
986 self.log.warn("Error loading server extension %s", modulename,
987 exc_info=True)
987 exc_info=True)
988
988
989 @catch_config_error
989 @catch_config_error
990 def initialize(self, argv=None):
990 def initialize(self, argv=None):
991 super(NotebookApp, self).initialize(argv)
991 super(NotebookApp, self).initialize(argv)
992 self.init_logging()
992 self.init_logging()
993 self.init_configurables()
993 self.init_configurables()
994 self.init_components()
994 self.init_components()
995 self.init_webapp()
995 self.init_webapp()
996 self.init_terminals()
996 self.init_terminals()
997 self.init_signal()
997 self.init_signal()
998 self.init_server_extensions()
998 self.init_server_extensions()
999
999
1000 def cleanup_kernels(self):
1000 def cleanup_kernels(self):
1001 """Shutdown all kernels.
1001 """Shutdown all kernels.
1002
1002
1003 The kernels will shutdown themselves when this process no longer exists,
1003 The kernels will shutdown themselves when this process no longer exists,
1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1005 """
1005 """
1006 self.log.info('Shutting down kernels')
1006 self.log.info('Shutting down kernels')
1007 self.kernel_manager.shutdown_all()
1007 self.kernel_manager.shutdown_all()
1008
1008
1009 def notebook_info(self):
1009 def notebook_info(self):
1010 "Return the current working directory and the server url information"
1010 "Return the current working directory and the server url information"
1011 info = self.contents_manager.info_string() + "\n"
1011 info = self.contents_manager.info_string() + "\n"
1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1014
1014
1015 def server_info(self):
1015 def server_info(self):
1016 """Return a JSONable dict of information about this server."""
1016 """Return a JSONable dict of information about this server."""
1017 return {'url': self.connection_url,
1017 return {'url': self.connection_url,
1018 'hostname': self.ip if self.ip else 'localhost',
1018 'hostname': self.ip if self.ip else 'localhost',
1019 'port': self.port,
1019 'port': self.port,
1020 'secure': bool(self.certfile),
1020 'secure': bool(self.certfile),
1021 'base_url': self.base_url,
1021 'base_url': self.base_url,
1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1023 'pid': os.getpid()
1023 'pid': os.getpid()
1024 }
1024 }
1025
1025
1026 def write_server_info_file(self):
1026 def write_server_info_file(self):
1027 """Write the result of server_info() to the JSON file info_file."""
1027 """Write the result of server_info() to the JSON file info_file."""
1028 with open(self.info_file, 'w') as f:
1028 with open(self.info_file, 'w') as f:
1029 json.dump(self.server_info(), f, indent=2)
1029 json.dump(self.server_info(), f, indent=2)
1030
1030
1031 def remove_server_info_file(self):
1031 def remove_server_info_file(self):
1032 """Remove the nbserver-<pid>.json file created for this server.
1032 """Remove the nbserver-<pid>.json file created for this server.
1033
1033
1034 Ignores the error raised when the file has already been removed.
1034 Ignores the error raised when the file has already been removed.
1035 """
1035 """
1036 try:
1036 try:
1037 os.unlink(self.info_file)
1037 os.unlink(self.info_file)
1038 except OSError as e:
1038 except OSError as e:
1039 if e.errno != errno.ENOENT:
1039 if e.errno != errno.ENOENT:
1040 raise
1040 raise
1041
1041
1042 def start(self):
1042 def start(self):
1043 """ Start the IPython Notebook server app, after initialization
1043 """ Start the IPython Notebook server app, after initialization
1044
1044
1045 This method takes no arguments so all configuration and initialization
1045 This method takes no arguments so all configuration and initialization
1046 must be done prior to calling this method."""
1046 must be done prior to calling this method."""
1047 if self.subapp is not None:
1047 if self.subapp is not None:
1048 return self.subapp.start()
1048 return self.subapp.start()
1049
1049
1050 info = self.log.info
1050 info = self.log.info
1051 for line in self.notebook_info().split("\n"):
1051 for line in self.notebook_info().split("\n"):
1052 info(line)
1052 info(line)
1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1054
1054
1055 self.write_server_info_file()
1055 self.write_server_info_file()
1056
1056
1057 if self.open_browser or self.file_to_run:
1057 if self.open_browser or self.file_to_run:
1058 try:
1058 try:
1059 browser = webbrowser.get(self.browser or None)
1059 browser = webbrowser.get(self.browser or None)
1060 except webbrowser.Error as e:
1060 except webbrowser.Error as e:
1061 self.log.warn('No web browser found: %s.' % e)
1061 self.log.warn('No web browser found: %s.' % e)
1062 browser = None
1062 browser = None
1063
1063
1064 if self.file_to_run:
1064 if self.file_to_run:
1065 if not os.path.exists(self.file_to_run):
1065 if not os.path.exists(self.file_to_run):
1066 self.log.critical("%s does not exist" % self.file_to_run)
1066 self.log.critical("%s does not exist" % self.file_to_run)
1067 self.exit(1)
1067 self.exit(1)
1068
1068
1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1071 else:
1071 else:
1072 uri = 'tree'
1072 uri = 'tree'
1073 if browser:
1073 if browser:
1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1075 new=2)
1075 new=2)
1076 threading.Thread(target=b).start()
1076 threading.Thread(target=b).start()
1077
1077
1078 self.io_loop = ioloop.IOLoop.current()
1078 self.io_loop = ioloop.IOLoop.current()
1079 if sys.platform.startswith('win'):
1079 if sys.platform.startswith('win'):
1080 # add no-op to wake every 5s
1080 # add no-op to wake every 5s
1081 # to handle signals that may be ignored by the inner loop
1081 # to handle signals that may be ignored by the inner loop
1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1083 pc.start()
1083 pc.start()
1084 try:
1084 try:
1085 self.io_loop.start()
1085 self.io_loop.start()
1086 except KeyboardInterrupt:
1086 except KeyboardInterrupt:
1087 info("Interrupted...")
1087 info("Interrupted...")
1088 finally:
1088 finally:
1089 self.cleanup_kernels()
1089 self.cleanup_kernels()
1090 self.remove_server_info_file()
1090 self.remove_server_info_file()
1091
1091
1092 def stop(self):
1092 def stop(self):
1093 def _stop():
1093 def _stop():
1094 self.http_server.stop()
1094 self.http_server.stop()
1095 self.io_loop.stop()
1095 self.io_loop.stop()
1096 self.io_loop.add_callback(_stop)
1096 self.io_loop.add_callback(_stop)
1097
1097
1098
1098
1099 def list_running_servers(profile='default'):
1099 def list_running_servers(profile='default'):
1100 """Iterate over the server info files of running notebook servers.
1100 """Iterate over the server info files of running notebook servers.
1101
1101
1102 Given a profile name, find nbserver-* files in the security directory of
1102 Given a profile name, find nbserver-* files in the security directory of
1103 that profile, and yield dicts of their information, each one pertaining to
1103 that profile, and yield dicts of their information, each one pertaining to
1104 a currently running notebook server instance.
1104 a currently running notebook server instance.
1105 """
1105 """
1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1107 for file in os.listdir(pd.security_dir):
1107 for file in os.listdir(pd.security_dir):
1108 if file.startswith('nbserver-'):
1108 if file.startswith('nbserver-'):
1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1110 info = json.load(f)
1110 info = json.load(f)
1111
1111
1112 # Simple check whether that process is really still running
1112 # Simple check whether that process is really still running
1113 # Also remove leftover files from IPython 2.x without a pid field
1113 # Also remove leftover files from IPython 2.x without a pid field
1114 if ('pid' in info) and check_pid(info['pid']):
1114 if ('pid' in info) and check_pid(info['pid']):
1115 yield info
1115 yield info
1116 else:
1116 else:
1117 # If the process has died, try to delete its info file
1117 # If the process has died, try to delete its info file
1118 try:
1118 try:
1119 os.unlink(file)
1119 os.unlink(file)
1120 except OSError:
1120 except OSError:
1121 pass # TODO: This should warn or log or something
1121 pass # TODO: This should warn or log or something
1122 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1123 # Main entry point
1123 # Main entry point
1124 #-----------------------------------------------------------------------------
1124 #-----------------------------------------------------------------------------
1125
1125
1126 launch_new_instance = NotebookApp.launch_instance
1126 launch_new_instance = NotebookApp.launch_instance
1127
1127
@@ -1,162 +1,162 b''
1 """Manage IPython.parallel clusters in the notebook."""
1 """Manage IPython.parallel clusters in the notebook."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from tornado import web
6 from tornado import web
7
7
8 from IPython.config.configurable import LoggingConfigurable
8 from IPython.config.configurable import LoggingConfigurable
9 from IPython.utils.traitlets import Dict, Instance, Float
9 from IPython.utils.traitlets import Dict, Instance, Float
10 from IPython.core.profileapp import list_profiles_in
10 from IPython.core.profileapp import list_profiles_in
11 from IPython.core.profiledir import ProfileDir
11 from IPython.core.profiledir import ProfileDir
12 from IPython.utils import py3compat
12 from IPython.utils import py3compat
13 from IPython.utils.path import get_ipython_dir
13 from IPython.utils.path import get_ipython_dir
14
14
15
15
16 class ClusterManager(LoggingConfigurable):
16 class ClusterManager(LoggingConfigurable):
17
17
18 profiles = Dict()
18 profiles = Dict()
19
19
20 delay = Float(1., config=True,
20 delay = Float(1., config=True,
21 help="delay (in s) between starting the controller and the engines")
21 help="delay (in s) between starting the controller and the engines")
22
22
23 loop = Instance('zmq.eventloop.ioloop.IOLoop')
23 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=True)
24 def _loop_default(self):
24 def _loop_default(self):
25 from zmq.eventloop.ioloop import IOLoop
25 from zmq.eventloop.ioloop import IOLoop
26 return IOLoop.instance()
26 return IOLoop.instance()
27
27
28 def build_launchers(self, profile_dir):
28 def build_launchers(self, profile_dir):
29 from IPython.parallel.apps.ipclusterapp import IPClusterStart
29 from IPython.parallel.apps.ipclusterapp import IPClusterStart
30
30
31 class DummyIPClusterStart(IPClusterStart):
31 class DummyIPClusterStart(IPClusterStart):
32 """Dummy subclass to skip init steps that conflict with global app.
32 """Dummy subclass to skip init steps that conflict with global app.
33
33
34 Instantiating and initializing this class should result in fully configured
34 Instantiating and initializing this class should result in fully configured
35 launchers, but no other side effects or state.
35 launchers, but no other side effects or state.
36 """
36 """
37
37
38 def init_signal(self):
38 def init_signal(self):
39 pass
39 pass
40 def reinit_logging(self):
40 def reinit_logging(self):
41 pass
41 pass
42
42
43 starter = DummyIPClusterStart(log=self.log)
43 starter = DummyIPClusterStart(log=self.log)
44 starter.initialize(['--profile-dir', profile_dir])
44 starter.initialize(['--profile-dir', profile_dir])
45 cl = starter.controller_launcher
45 cl = starter.controller_launcher
46 esl = starter.engine_launcher
46 esl = starter.engine_launcher
47 n = starter.n
47 n = starter.n
48 return cl, esl, n
48 return cl, esl, n
49
49
50 def get_profile_dir(self, name, path):
50 def get_profile_dir(self, name, path):
51 p = ProfileDir.find_profile_dir_by_name(path,name=name)
51 p = ProfileDir.find_profile_dir_by_name(path,name=name)
52 return p.location
52 return p.location
53
53
54 def update_profiles(self):
54 def update_profiles(self):
55 """List all profiles in the ipython_dir and cwd.
55 """List all profiles in the ipython_dir and cwd.
56 """
56 """
57
57
58 stale = set(self.profiles)
58 stale = set(self.profiles)
59 for path in [get_ipython_dir(), py3compat.getcwd()]:
59 for path in [get_ipython_dir(), py3compat.getcwd()]:
60 for profile in list_profiles_in(path):
60 for profile in list_profiles_in(path):
61 if profile in stale:
61 if profile in stale:
62 stale.remove(profile)
62 stale.remove(profile)
63 pd = self.get_profile_dir(profile, path)
63 pd = self.get_profile_dir(profile, path)
64 if profile not in self.profiles:
64 if profile not in self.profiles:
65 self.log.debug("Adding cluster profile '%s'", profile)
65 self.log.debug("Adding cluster profile '%s'", profile)
66 self.profiles[profile] = {
66 self.profiles[profile] = {
67 'profile': profile,
67 'profile': profile,
68 'profile_dir': pd,
68 'profile_dir': pd,
69 'status': 'stopped'
69 'status': 'stopped'
70 }
70 }
71 for profile in stale:
71 for profile in stale:
72 # remove profiles that no longer exist
72 # remove profiles that no longer exist
73 self.log.debug("Profile '%s' no longer exists", profile)
73 self.log.debug("Profile '%s' no longer exists", profile)
74 self.profiles.pop(stale)
74 self.profiles.pop(stale)
75
75
76 def list_profiles(self):
76 def list_profiles(self):
77 self.update_profiles()
77 self.update_profiles()
78 # sorted list, but ensure that 'default' always comes first
78 # sorted list, but ensure that 'default' always comes first
79 default_first = lambda name: name if name != 'default' else ''
79 default_first = lambda name: name if name != 'default' else ''
80 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
80 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
81 return result
81 return result
82
82
83 def check_profile(self, profile):
83 def check_profile(self, profile):
84 if profile not in self.profiles:
84 if profile not in self.profiles:
85 raise web.HTTPError(404, u'profile not found')
85 raise web.HTTPError(404, u'profile not found')
86
86
87 def profile_info(self, profile):
87 def profile_info(self, profile):
88 self.check_profile(profile)
88 self.check_profile(profile)
89 result = {}
89 result = {}
90 data = self.profiles.get(profile)
90 data = self.profiles.get(profile)
91 result['profile'] = profile
91 result['profile'] = profile
92 result['profile_dir'] = data['profile_dir']
92 result['profile_dir'] = data['profile_dir']
93 result['status'] = data['status']
93 result['status'] = data['status']
94 if 'n' in data:
94 if 'n' in data:
95 result['n'] = data['n']
95 result['n'] = data['n']
96 return result
96 return result
97
97
98 def start_cluster(self, profile, n=None):
98 def start_cluster(self, profile, n=None):
99 """Start a cluster for a given profile."""
99 """Start a cluster for a given profile."""
100 self.check_profile(profile)
100 self.check_profile(profile)
101 data = self.profiles[profile]
101 data = self.profiles[profile]
102 if data['status'] == 'running':
102 if data['status'] == 'running':
103 raise web.HTTPError(409, u'cluster already running')
103 raise web.HTTPError(409, u'cluster already running')
104 cl, esl, default_n = self.build_launchers(data['profile_dir'])
104 cl, esl, default_n = self.build_launchers(data['profile_dir'])
105 n = n if n is not None else default_n
105 n = n if n is not None else default_n
106 def clean_data():
106 def clean_data():
107 data.pop('controller_launcher',None)
107 data.pop('controller_launcher',None)
108 data.pop('engine_set_launcher',None)
108 data.pop('engine_set_launcher',None)
109 data.pop('n',None)
109 data.pop('n',None)
110 data['status'] = 'stopped'
110 data['status'] = 'stopped'
111 def engines_stopped(r):
111 def engines_stopped(r):
112 self.log.debug('Engines stopped')
112 self.log.debug('Engines stopped')
113 if cl.running:
113 if cl.running:
114 cl.stop()
114 cl.stop()
115 clean_data()
115 clean_data()
116 esl.on_stop(engines_stopped)
116 esl.on_stop(engines_stopped)
117 def controller_stopped(r):
117 def controller_stopped(r):
118 self.log.debug('Controller stopped')
118 self.log.debug('Controller stopped')
119 if esl.running:
119 if esl.running:
120 esl.stop()
120 esl.stop()
121 clean_data()
121 clean_data()
122 cl.on_stop(controller_stopped)
122 cl.on_stop(controller_stopped)
123 loop = self.loop
123 loop = self.loop
124
124
125 def start():
125 def start():
126 """start the controller, then the engines after a delay"""
126 """start the controller, then the engines after a delay"""
127 cl.start()
127 cl.start()
128 loop.add_timeout(self.loop.time() + self.delay, lambda : esl.start(n))
128 loop.add_timeout(self.loop.time() + self.delay, lambda : esl.start(n))
129 self.loop.add_callback(start)
129 self.loop.add_callback(start)
130
130
131 self.log.debug('Cluster started')
131 self.log.debug('Cluster started')
132 data['controller_launcher'] = cl
132 data['controller_launcher'] = cl
133 data['engine_set_launcher'] = esl
133 data['engine_set_launcher'] = esl
134 data['n'] = n
134 data['n'] = n
135 data['status'] = 'running'
135 data['status'] = 'running'
136 return self.profile_info(profile)
136 return self.profile_info(profile)
137
137
138 def stop_cluster(self, profile):
138 def stop_cluster(self, profile):
139 """Stop a cluster for a given profile."""
139 """Stop a cluster for a given profile."""
140 self.check_profile(profile)
140 self.check_profile(profile)
141 data = self.profiles[profile]
141 data = self.profiles[profile]
142 if data['status'] == 'stopped':
142 if data['status'] == 'stopped':
143 raise web.HTTPError(409, u'cluster not running')
143 raise web.HTTPError(409, u'cluster not running')
144 data = self.profiles[profile]
144 data = self.profiles[profile]
145 cl = data['controller_launcher']
145 cl = data['controller_launcher']
146 esl = data['engine_set_launcher']
146 esl = data['engine_set_launcher']
147 if cl.running:
147 if cl.running:
148 cl.stop()
148 cl.stop()
149 if esl.running:
149 if esl.running:
150 esl.stop()
150 esl.stop()
151 # Return a temp info dict, the real one is updated in the on_stop
151 # Return a temp info dict, the real one is updated in the on_stop
152 # logic above.
152 # logic above.
153 result = {
153 result = {
154 'profile': data['profile'],
154 'profile': data['profile'],
155 'profile_dir': data['profile_dir'],
155 'profile_dir': data['profile_dir'],
156 'status': 'stopped'
156 'status': 'stopped'
157 }
157 }
158 return result
158 return result
159
159
160 def stop_all_clusters(self):
160 def stop_all_clusters(self):
161 for p in self.profiles.keys():
161 for p in self.profiles.keys():
162 self.stop_cluster(p)
162 self.stop_cluster(p)
@@ -1,497 +1,497 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25 from .trait_types import Color
25 from .trait_types import Color
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Classes
28 # Classes
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 class CallbackDispatcher(LoggingConfigurable):
30 class CallbackDispatcher(LoggingConfigurable):
31 """A structure for registering and running callbacks"""
31 """A structure for registering and running callbacks"""
32 callbacks = List()
32 callbacks = List()
33
33
34 def __call__(self, *args, **kwargs):
34 def __call__(self, *args, **kwargs):
35 """Call all of the registered callbacks."""
35 """Call all of the registered callbacks."""
36 value = None
36 value = None
37 for callback in self.callbacks:
37 for callback in self.callbacks:
38 try:
38 try:
39 local_value = callback(*args, **kwargs)
39 local_value = callback(*args, **kwargs)
40 except Exception as e:
40 except Exception as e:
41 ip = get_ipython()
41 ip = get_ipython()
42 if ip is None:
42 if ip is None:
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
44 else:
44 else:
45 ip.showtraceback()
45 ip.showtraceback()
46 else:
46 else:
47 value = local_value if local_value is not None else value
47 value = local_value if local_value is not None else value
48 return value
48 return value
49
49
50 def register_callback(self, callback, remove=False):
50 def register_callback(self, callback, remove=False):
51 """(Un)Register a callback
51 """(Un)Register a callback
52
52
53 Parameters
53 Parameters
54 ----------
54 ----------
55 callback: method handle
55 callback: method handle
56 Method to be registered or unregistered.
56 Method to be registered or unregistered.
57 remove=False: bool
57 remove=False: bool
58 Whether to unregister the callback."""
58 Whether to unregister the callback."""
59
59
60 # (Un)Register the callback.
60 # (Un)Register the callback.
61 if remove and callback in self.callbacks:
61 if remove and callback in self.callbacks:
62 self.callbacks.remove(callback)
62 self.callbacks.remove(callback)
63 elif not remove and callback not in self.callbacks:
63 elif not remove and callback not in self.callbacks:
64 self.callbacks.append(callback)
64 self.callbacks.append(callback)
65
65
66 def _show_traceback(method):
66 def _show_traceback(method):
67 """decorator for showing tracebacks in IPython"""
67 """decorator for showing tracebacks in IPython"""
68 def m(self, *args, **kwargs):
68 def m(self, *args, **kwargs):
69 try:
69 try:
70 return(method(self, *args, **kwargs))
70 return(method(self, *args, **kwargs))
71 except Exception as e:
71 except Exception as e:
72 ip = get_ipython()
72 ip = get_ipython()
73 if ip is None:
73 if ip is None:
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
75 else:
75 else:
76 ip.showtraceback()
76 ip.showtraceback()
77 return m
77 return m
78
78
79
79
80 def register(key=None):
80 def register(key=None):
81 """Returns a decorator registering a widget class in the widget registry.
81 """Returns a decorator registering a widget class in the widget registry.
82 If no key is provided, the class name is used as a key. A key is
82 If no key is provided, the class name is used as a key. A key is
83 provided for each core IPython widget so that the frontend can use
83 provided for each core IPython widget so that the frontend can use
84 this key regardless of the language of the kernel"""
84 this key regardless of the language of the kernel"""
85 def wrap(widget):
85 def wrap(widget):
86 l = key if key is not None else widget.__module__ + widget.__name__
86 l = key if key is not None else widget.__module__ + widget.__name__
87 Widget.widget_types[l] = widget
87 Widget.widget_types[l] = widget
88 return widget
88 return widget
89 return wrap
89 return wrap
90
90
91
91
92 class Widget(LoggingConfigurable):
92 class Widget(LoggingConfigurable):
93 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
94 # Class attributes
94 # Class attributes
95 #-------------------------------------------------------------------------
95 #-------------------------------------------------------------------------
96 _widget_construction_callback = None
96 _widget_construction_callback = None
97 widgets = {}
97 widgets = {}
98 widget_types = {}
98 widget_types = {}
99
99
100 @staticmethod
100 @staticmethod
101 def on_widget_constructed(callback):
101 def on_widget_constructed(callback):
102 """Registers a callback to be called when a widget is constructed.
102 """Registers a callback to be called when a widget is constructed.
103
103
104 The callback must have the following signature:
104 The callback must have the following signature:
105 callback(widget)"""
105 callback(widget)"""
106 Widget._widget_construction_callback = callback
106 Widget._widget_construction_callback = callback
107
107
108 @staticmethod
108 @staticmethod
109 def _call_widget_constructed(widget):
109 def _call_widget_constructed(widget):
110 """Static method, called when a widget is constructed."""
110 """Static method, called when a widget is constructed."""
111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
112 Widget._widget_construction_callback(widget)
112 Widget._widget_construction_callback(widget)
113
113
114 @staticmethod
114 @staticmethod
115 def handle_comm_opened(comm, msg):
115 def handle_comm_opened(comm, msg):
116 """Static method, called when a widget is constructed."""
116 """Static method, called when a widget is constructed."""
117 widget_class = import_item(msg['content']['data']['widget_class'])
117 widget_class = import_item(msg['content']['data']['widget_class'])
118 widget = widget_class(comm=comm)
118 widget = widget_class(comm=comm)
119
119
120
120
121 #-------------------------------------------------------------------------
121 #-------------------------------------------------------------------------
122 # Traits
122 # Traits
123 #-------------------------------------------------------------------------
123 #-------------------------------------------------------------------------
124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
125 in which to find _model_name. If empty, look in the global registry.""")
125 in which to find _model_name. If empty, look in the global registry.""")
126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
127 registered in the front-end to create and sync this widget with.""")
127 registered in the front-end to create and sync this widget with.""")
128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
129 If empty, look in the global registry.""", sync=True)
129 If empty, look in the global registry.""", sync=True)
130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
131 to use to represent the widget.""", sync=True)
131 to use to represent the widget.""", sync=True)
132 comm = Instance('IPython.kernel.comm.Comm')
132 comm = Instance('IPython.kernel.comm.Comm', allow_none=True)
133
133
134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
135 front-end can send before receiving an idle msg from the back-end.""")
135 front-end can send before receiving an idle msg from the back-end.""")
136
136
137 version = Int(0, sync=True, help="""Widget's version""")
137 version = Int(0, sync=True, help="""Widget's version""")
138 keys = List()
138 keys = List()
139 def _keys_default(self):
139 def _keys_default(self):
140 return [name for name in self.traits(sync=True)]
140 return [name for name in self.traits(sync=True)]
141
141
142 _property_lock = Tuple((None, None))
142 _property_lock = Tuple((None, None))
143 _send_state_lock = Int(0)
143 _send_state_lock = Int(0)
144 _states_to_send = Set()
144 _states_to_send = Set()
145 _display_callbacks = Instance(CallbackDispatcher, ())
145 _display_callbacks = Instance(CallbackDispatcher, ())
146 _msg_callbacks = Instance(CallbackDispatcher, ())
146 _msg_callbacks = Instance(CallbackDispatcher, ())
147
147
148 #-------------------------------------------------------------------------
148 #-------------------------------------------------------------------------
149 # (Con/de)structor
149 # (Con/de)structor
150 #-------------------------------------------------------------------------
150 #-------------------------------------------------------------------------
151 def __init__(self, **kwargs):
151 def __init__(self, **kwargs):
152 """Public constructor"""
152 """Public constructor"""
153 self._model_id = kwargs.pop('model_id', None)
153 self._model_id = kwargs.pop('model_id', None)
154 super(Widget, self).__init__(**kwargs)
154 super(Widget, self).__init__(**kwargs)
155
155
156 Widget._call_widget_constructed(self)
156 Widget._call_widget_constructed(self)
157 self.open()
157 self.open()
158
158
159 def __del__(self):
159 def __del__(self):
160 """Object disposal"""
160 """Object disposal"""
161 self.close()
161 self.close()
162
162
163 #-------------------------------------------------------------------------
163 #-------------------------------------------------------------------------
164 # Properties
164 # Properties
165 #-------------------------------------------------------------------------
165 #-------------------------------------------------------------------------
166
166
167 def open(self):
167 def open(self):
168 """Open a comm to the frontend if one isn't already open."""
168 """Open a comm to the frontend if one isn't already open."""
169 if self.comm is None:
169 if self.comm is None:
170 args = dict(target_name='ipython.widget',
170 args = dict(target_name='ipython.widget',
171 data={'model_name': self._model_name,
171 data={'model_name': self._model_name,
172 'model_module': self._model_module})
172 'model_module': self._model_module})
173 if self._model_id is not None:
173 if self._model_id is not None:
174 args['comm_id'] = self._model_id
174 args['comm_id'] = self._model_id
175 self.comm = Comm(**args)
175 self.comm = Comm(**args)
176
176
177 def _comm_changed(self, name, new):
177 def _comm_changed(self, name, new):
178 """Called when the comm is changed."""
178 """Called when the comm is changed."""
179 if new is None:
179 if new is None:
180 return
180 return
181 self._model_id = self.model_id
181 self._model_id = self.model_id
182
182
183 self.comm.on_msg(self._handle_msg)
183 self.comm.on_msg(self._handle_msg)
184 Widget.widgets[self.model_id] = self
184 Widget.widgets[self.model_id] = self
185
185
186 # first update
186 # first update
187 self.send_state()
187 self.send_state()
188
188
189 @property
189 @property
190 def model_id(self):
190 def model_id(self):
191 """Gets the model id of this widget.
191 """Gets the model id of this widget.
192
192
193 If a Comm doesn't exist yet, a Comm will be created automagically."""
193 If a Comm doesn't exist yet, a Comm will be created automagically."""
194 return self.comm.comm_id
194 return self.comm.comm_id
195
195
196 #-------------------------------------------------------------------------
196 #-------------------------------------------------------------------------
197 # Methods
197 # Methods
198 #-------------------------------------------------------------------------
198 #-------------------------------------------------------------------------
199
199
200 def close(self):
200 def close(self):
201 """Close method.
201 """Close method.
202
202
203 Closes the underlying comm.
203 Closes the underlying comm.
204 When the comm is closed, all of the widget views are automatically
204 When the comm is closed, all of the widget views are automatically
205 removed from the front-end."""
205 removed from the front-end."""
206 if self.comm is not None:
206 if self.comm is not None:
207 Widget.widgets.pop(self.model_id, None)
207 Widget.widgets.pop(self.model_id, None)
208 self.comm.close()
208 self.comm.close()
209 self.comm = None
209 self.comm = None
210
210
211 def send_state(self, key=None):
211 def send_state(self, key=None):
212 """Sends the widget state, or a piece of it, to the front-end.
212 """Sends the widget state, or a piece of it, to the front-end.
213
213
214 Parameters
214 Parameters
215 ----------
215 ----------
216 key : unicode, or iterable (optional)
216 key : unicode, or iterable (optional)
217 A single property's name or iterable of property names to sync with the front-end.
217 A single property's name or iterable of property names to sync with the front-end.
218 """
218 """
219 self._send({
219 self._send({
220 "method" : "update",
220 "method" : "update",
221 "state" : self.get_state(key=key)
221 "state" : self.get_state(key=key)
222 })
222 })
223
223
224 def get_state(self, key=None):
224 def get_state(self, key=None):
225 """Gets the widget state, or a piece of it.
225 """Gets the widget state, or a piece of it.
226
226
227 Parameters
227 Parameters
228 ----------
228 ----------
229 key : unicode or iterable (optional)
229 key : unicode or iterable (optional)
230 A single property's name or iterable of property names to get.
230 A single property's name or iterable of property names to get.
231 """
231 """
232 if key is None:
232 if key is None:
233 keys = self.keys
233 keys = self.keys
234 elif isinstance(key, string_types):
234 elif isinstance(key, string_types):
235 keys = [key]
235 keys = [key]
236 elif isinstance(key, collections.Iterable):
236 elif isinstance(key, collections.Iterable):
237 keys = key
237 keys = key
238 else:
238 else:
239 raise ValueError("key must be a string, an iterable of keys, or None")
239 raise ValueError("key must be a string, an iterable of keys, or None")
240 state = {}
240 state = {}
241 for k in keys:
241 for k in keys:
242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
243 value = getattr(self, k)
243 value = getattr(self, k)
244 state[k] = f(value)
244 state[k] = f(value)
245 return state
245 return state
246
246
247 def set_state(self, sync_data):
247 def set_state(self, sync_data):
248 """Called when a state is received from the front-end."""
248 """Called when a state is received from the front-end."""
249 for name in self.keys:
249 for name in self.keys:
250 if name in sync_data:
250 if name in sync_data:
251 json_value = sync_data[name]
251 json_value = sync_data[name]
252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
253 with self._lock_property(name, json_value):
253 with self._lock_property(name, json_value):
254 setattr(self, name, from_json(json_value))
254 setattr(self, name, from_json(json_value))
255
255
256 def send(self, content):
256 def send(self, content):
257 """Sends a custom msg to the widget model in the front-end.
257 """Sends a custom msg to the widget model in the front-end.
258
258
259 Parameters
259 Parameters
260 ----------
260 ----------
261 content : dict
261 content : dict
262 Content of the message to send.
262 Content of the message to send.
263 """
263 """
264 self._send({"method": "custom", "content": content})
264 self._send({"method": "custom", "content": content})
265
265
266 def on_msg(self, callback, remove=False):
266 def on_msg(self, callback, remove=False):
267 """(Un)Register a custom msg receive callback.
267 """(Un)Register a custom msg receive callback.
268
268
269 Parameters
269 Parameters
270 ----------
270 ----------
271 callback: callable
271 callback: callable
272 callback will be passed two arguments when a message arrives::
272 callback will be passed two arguments when a message arrives::
273
273
274 callback(widget, content)
274 callback(widget, content)
275
275
276 remove: bool
276 remove: bool
277 True if the callback should be unregistered."""
277 True if the callback should be unregistered."""
278 self._msg_callbacks.register_callback(callback, remove=remove)
278 self._msg_callbacks.register_callback(callback, remove=remove)
279
279
280 def on_displayed(self, callback, remove=False):
280 def on_displayed(self, callback, remove=False):
281 """(Un)Register a widget displayed callback.
281 """(Un)Register a widget displayed callback.
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 callback: method handler
285 callback: method handler
286 Must have a signature of::
286 Must have a signature of::
287
287
288 callback(widget, **kwargs)
288 callback(widget, **kwargs)
289
289
290 kwargs from display are passed through without modification.
290 kwargs from display are passed through without modification.
291 remove: bool
291 remove: bool
292 True if the callback should be unregistered."""
292 True if the callback should be unregistered."""
293 self._display_callbacks.register_callback(callback, remove=remove)
293 self._display_callbacks.register_callback(callback, remove=remove)
294
294
295 def add_trait(self, traitname, trait):
295 def add_trait(self, traitname, trait):
296 """Dynamically add a trait attribute to the Widget."""
296 """Dynamically add a trait attribute to the Widget."""
297 super(Widget, self).add_trait(traitname, trait)
297 super(Widget, self).add_trait(traitname, trait)
298 if trait.get_metadata('sync'):
298 if trait.get_metadata('sync'):
299 self.keys.append(traitname)
299 self.keys.append(traitname)
300 self.send_state(traitname)
300 self.send_state(traitname)
301
301
302 #-------------------------------------------------------------------------
302 #-------------------------------------------------------------------------
303 # Support methods
303 # Support methods
304 #-------------------------------------------------------------------------
304 #-------------------------------------------------------------------------
305 @contextmanager
305 @contextmanager
306 def _lock_property(self, key, value):
306 def _lock_property(self, key, value):
307 """Lock a property-value pair.
307 """Lock a property-value pair.
308
308
309 The value should be the JSON state of the property.
309 The value should be the JSON state of the property.
310
310
311 NOTE: This, in addition to the single lock for all state changes, is
311 NOTE: This, in addition to the single lock for all state changes, is
312 flawed. In the future we may want to look into buffering state changes
312 flawed. In the future we may want to look into buffering state changes
313 back to the front-end."""
313 back to the front-end."""
314 self._property_lock = (key, value)
314 self._property_lock = (key, value)
315 try:
315 try:
316 yield
316 yield
317 finally:
317 finally:
318 self._property_lock = (None, None)
318 self._property_lock = (None, None)
319
319
320 @contextmanager
320 @contextmanager
321 def hold_sync(self):
321 def hold_sync(self):
322 """Hold syncing any state until the context manager is released"""
322 """Hold syncing any state until the context manager is released"""
323 # We increment a value so that this can be nested. Syncing will happen when
323 # We increment a value so that this can be nested. Syncing will happen when
324 # all levels have been released.
324 # all levels have been released.
325 self._send_state_lock += 1
325 self._send_state_lock += 1
326 try:
326 try:
327 yield
327 yield
328 finally:
328 finally:
329 self._send_state_lock -=1
329 self._send_state_lock -=1
330 if self._send_state_lock == 0:
330 if self._send_state_lock == 0:
331 self.send_state(self._states_to_send)
331 self.send_state(self._states_to_send)
332 self._states_to_send.clear()
332 self._states_to_send.clear()
333
333
334 def _should_send_property(self, key, value):
334 def _should_send_property(self, key, value):
335 """Check the property lock (property_lock)"""
335 """Check the property lock (property_lock)"""
336 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
336 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
337 if (key == self._property_lock[0]
337 if (key == self._property_lock[0]
338 and to_json(value) == self._property_lock[1]):
338 and to_json(value) == self._property_lock[1]):
339 return False
339 return False
340 elif self._send_state_lock > 0:
340 elif self._send_state_lock > 0:
341 self._states_to_send.add(key)
341 self._states_to_send.add(key)
342 return False
342 return False
343 else:
343 else:
344 return True
344 return True
345
345
346 # Event handlers
346 # Event handlers
347 @_show_traceback
347 @_show_traceback
348 def _handle_msg(self, msg):
348 def _handle_msg(self, msg):
349 """Called when a msg is received from the front-end"""
349 """Called when a msg is received from the front-end"""
350 data = msg['content']['data']
350 data = msg['content']['data']
351 method = data['method']
351 method = data['method']
352
352
353 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
353 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
354 if method == 'backbone':
354 if method == 'backbone':
355 if 'sync_data' in data:
355 if 'sync_data' in data:
356 sync_data = data['sync_data']
356 sync_data = data['sync_data']
357 self.set_state(sync_data) # handles all methods
357 self.set_state(sync_data) # handles all methods
358
358
359 # Handle a state request.
359 # Handle a state request.
360 elif method == 'request_state':
360 elif method == 'request_state':
361 self.send_state()
361 self.send_state()
362
362
363 # Handle a custom msg from the front-end.
363 # Handle a custom msg from the front-end.
364 elif method == 'custom':
364 elif method == 'custom':
365 if 'content' in data:
365 if 'content' in data:
366 self._handle_custom_msg(data['content'])
366 self._handle_custom_msg(data['content'])
367
367
368 # Catch remainder.
368 # Catch remainder.
369 else:
369 else:
370 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
370 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
371
371
372 def _handle_custom_msg(self, content):
372 def _handle_custom_msg(self, content):
373 """Called when a custom msg is received."""
373 """Called when a custom msg is received."""
374 self._msg_callbacks(self, content)
374 self._msg_callbacks(self, content)
375
375
376 def _notify_trait(self, name, old_value, new_value):
376 def _notify_trait(self, name, old_value, new_value):
377 """Called when a property has been changed."""
377 """Called when a property has been changed."""
378 # Trigger default traitlet callback machinery. This allows any user
378 # Trigger default traitlet callback machinery. This allows any user
379 # registered validation to be processed prior to allowing the widget
379 # registered validation to be processed prior to allowing the widget
380 # machinery to handle the state.
380 # machinery to handle the state.
381 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
381 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
382
382
383 # Send the state after the user registered callbacks for trait changes
383 # Send the state after the user registered callbacks for trait changes
384 # have all fired (allows for user to validate values).
384 # have all fired (allows for user to validate values).
385 if self.comm is not None and name in self.keys:
385 if self.comm is not None and name in self.keys:
386 # Make sure this isn't information that the front-end just sent us.
386 # Make sure this isn't information that the front-end just sent us.
387 if self._should_send_property(name, new_value):
387 if self._should_send_property(name, new_value):
388 # Send new state to front-end
388 # Send new state to front-end
389 self.send_state(key=name)
389 self.send_state(key=name)
390
390
391 def _handle_displayed(self, **kwargs):
391 def _handle_displayed(self, **kwargs):
392 """Called when a view has been displayed for this widget instance"""
392 """Called when a view has been displayed for this widget instance"""
393 self._display_callbacks(self, **kwargs)
393 self._display_callbacks(self, **kwargs)
394
394
395 def _trait_to_json(self, x):
395 def _trait_to_json(self, x):
396 """Convert a trait value to json
396 """Convert a trait value to json
397
397
398 Traverse lists/tuples and dicts and serialize their values as well.
398 Traverse lists/tuples and dicts and serialize their values as well.
399 Replace any widgets with their model_id
399 Replace any widgets with their model_id
400 """
400 """
401 if isinstance(x, dict):
401 if isinstance(x, dict):
402 return {k: self._trait_to_json(v) for k, v in x.items()}
402 return {k: self._trait_to_json(v) for k, v in x.items()}
403 elif isinstance(x, (list, tuple)):
403 elif isinstance(x, (list, tuple)):
404 return [self._trait_to_json(v) for v in x]
404 return [self._trait_to_json(v) for v in x]
405 elif isinstance(x, Widget):
405 elif isinstance(x, Widget):
406 return "IPY_MODEL_" + x.model_id
406 return "IPY_MODEL_" + x.model_id
407 else:
407 else:
408 return x # Value must be JSON-able
408 return x # Value must be JSON-able
409
409
410 def _trait_from_json(self, x):
410 def _trait_from_json(self, x):
411 """Convert json values to objects
411 """Convert json values to objects
412
412
413 Replace any strings representing valid model id values to Widget references.
413 Replace any strings representing valid model id values to Widget references.
414 """
414 """
415 if isinstance(x, dict):
415 if isinstance(x, dict):
416 return {k: self._trait_from_json(v) for k, v in x.items()}
416 return {k: self._trait_from_json(v) for k, v in x.items()}
417 elif isinstance(x, (list, tuple)):
417 elif isinstance(x, (list, tuple)):
418 return [self._trait_from_json(v) for v in x]
418 return [self._trait_from_json(v) for v in x]
419 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
419 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
420 # we want to support having child widgets at any level in a hierarchy
420 # we want to support having child widgets at any level in a hierarchy
421 # trusting that a widget UUID will not appear out in the wild
421 # trusting that a widget UUID will not appear out in the wild
422 return Widget.widgets[x[10:]]
422 return Widget.widgets[x[10:]]
423 else:
423 else:
424 return x
424 return x
425
425
426 def _ipython_display_(self, **kwargs):
426 def _ipython_display_(self, **kwargs):
427 """Called when `IPython.display.display` is called on the widget."""
427 """Called when `IPython.display.display` is called on the widget."""
428 # Show view.
428 # Show view.
429 if self._view_name is not None:
429 if self._view_name is not None:
430 self._send({"method": "display"})
430 self._send({"method": "display"})
431 self._handle_displayed(**kwargs)
431 self._handle_displayed(**kwargs)
432
432
433 def _send(self, msg):
433 def _send(self, msg):
434 """Sends a message to the model in the front-end."""
434 """Sends a message to the model in the front-end."""
435 self.comm.send(msg)
435 self.comm.send(msg)
436
436
437
437
438 class DOMWidget(Widget):
438 class DOMWidget(Widget):
439 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
439 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
440 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
440 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
441 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
441 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
442
442
443 width = CUnicode(sync=True)
443 width = CUnicode(sync=True)
444 height = CUnicode(sync=True)
444 height = CUnicode(sync=True)
445 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
445 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
446 padding = CUnicode(sync=True)
446 padding = CUnicode(sync=True)
447 margin = CUnicode(sync=True)
447 margin = CUnicode(sync=True)
448
448
449 color = Color(None, allow_none=True, sync=True)
449 color = Color(None, allow_none=True, sync=True)
450 background_color = Color(None, allow_none=True, sync=True)
450 background_color = Color(None, allow_none=True, sync=True)
451 border_color = Color(None, allow_none=True, sync=True)
451 border_color = Color(None, allow_none=True, sync=True)
452
452
453 border_width = CUnicode(sync=True)
453 border_width = CUnicode(sync=True)
454 border_radius = CUnicode(sync=True)
454 border_radius = CUnicode(sync=True)
455 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
455 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
456 'none',
456 'none',
457 'hidden',
457 'hidden',
458 'dotted',
458 'dotted',
459 'dashed',
459 'dashed',
460 'solid',
460 'solid',
461 'double',
461 'double',
462 'groove',
462 'groove',
463 'ridge',
463 'ridge',
464 'inset',
464 'inset',
465 'outset',
465 'outset',
466 'initial',
466 'initial',
467 'inherit', ''],
467 'inherit', ''],
468 default_value='', sync=True)
468 default_value='', sync=True)
469
469
470 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
470 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
471 'normal',
471 'normal',
472 'italic',
472 'italic',
473 'oblique',
473 'oblique',
474 'initial',
474 'initial',
475 'inherit', ''],
475 'inherit', ''],
476 default_value='', sync=True)
476 default_value='', sync=True)
477 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
477 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
478 'normal',
478 'normal',
479 'bold',
479 'bold',
480 'bolder',
480 'bolder',
481 'lighter',
481 'lighter',
482 'initial',
482 'initial',
483 'inherit', ''] + list(map(str, range(100,1000,100))),
483 'inherit', ''] + list(map(str, range(100,1000,100))),
484 default_value='', sync=True)
484 default_value='', sync=True)
485 font_size = CUnicode(sync=True)
485 font_size = CUnicode(sync=True)
486 font_family = Unicode(sync=True)
486 font_family = Unicode(sync=True)
487
487
488 def __init__(self, *pargs, **kwargs):
488 def __init__(self, *pargs, **kwargs):
489 super(DOMWidget, self).__init__(*pargs, **kwargs)
489 super(DOMWidget, self).__init__(*pargs, **kwargs)
490
490
491 def _validate_border(name, old, new):
491 def _validate_border(name, old, new):
492 if new is not None and new != '':
492 if new is not None and new != '':
493 if name != 'border_width' and not self.border_width:
493 if name != 'border_width' and not self.border_width:
494 self.border_width = 1
494 self.border_width = 1
495 if name != 'border_style' and self.border_style == '':
495 if name != 'border_style' and self.border_style == '':
496 self.border_style = 'solid'
496 self.border_style = 'solid'
497 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
497 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,156 +1,157 b''
1 """A client for in-process kernels."""
1 """A client for in-process kernels."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # IPython imports
14 # IPython imports
15 from IPython.kernel.inprocess.socket import DummySocket
15 from IPython.kernel.inprocess.socket import DummySocket
16 from IPython.utils.traitlets import Type, Instance
16 from IPython.utils.traitlets import Type, Instance
17 from IPython.kernel.clientabc import KernelClientABC
17 from IPython.kernel.clientabc import KernelClientABC
18 from IPython.kernel.client import KernelClient
18 from IPython.kernel.client import KernelClient
19
19
20 # Local imports
20 # Local imports
21 from .channels import (
21 from .channels import (
22 InProcessChannel,
22 InProcessChannel,
23 InProcessHBChannel,
23 InProcessHBChannel,
24
24
25 )
25 )
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Main kernel Client class
28 # Main kernel Client class
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class InProcessKernelClient(KernelClient):
31 class InProcessKernelClient(KernelClient):
32 """A client for an in-process kernel.
32 """A client for an in-process kernel.
33
33
34 This class implements the interface of
34 This class implements the interface of
35 `IPython.kernel.clientabc.KernelClientABC` and allows
35 `IPython.kernel.clientabc.KernelClientABC` and allows
36 (asynchronous) frontends to be used seamlessly with an in-process kernel.
36 (asynchronous) frontends to be used seamlessly with an in-process kernel.
37
37
38 See `IPython.kernel.client.KernelClient` for docstrings.
38 See `IPython.kernel.client.KernelClient` for docstrings.
39 """
39 """
40
40
41 # The classes to use for the various channels.
41 # The classes to use for the various channels.
42 shell_channel_class = Type(InProcessChannel)
42 shell_channel_class = Type(InProcessChannel)
43 iopub_channel_class = Type(InProcessChannel)
43 iopub_channel_class = Type(InProcessChannel)
44 stdin_channel_class = Type(InProcessChannel)
44 stdin_channel_class = Type(InProcessChannel)
45 hb_channel_class = Type(InProcessHBChannel)
45 hb_channel_class = Type(InProcessHBChannel)
46
46
47 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
47 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel',
48 allow_none=True)
48
49
49 #--------------------------------------------------------------------------
50 #--------------------------------------------------------------------------
50 # Channel management methods
51 # Channel management methods
51 #--------------------------------------------------------------------------
52 #--------------------------------------------------------------------------
52
53
53 def start_channels(self, *args, **kwargs):
54 def start_channels(self, *args, **kwargs):
54 super(InProcessKernelClient, self).start_channels(self)
55 super(InProcessKernelClient, self).start_channels(self)
55 self.kernel.frontends.append(self)
56 self.kernel.frontends.append(self)
56
57
57 @property
58 @property
58 def shell_channel(self):
59 def shell_channel(self):
59 if self._shell_channel is None:
60 if self._shell_channel is None:
60 self._shell_channel = self.shell_channel_class(self)
61 self._shell_channel = self.shell_channel_class(self)
61 return self._shell_channel
62 return self._shell_channel
62
63
63 @property
64 @property
64 def iopub_channel(self):
65 def iopub_channel(self):
65 if self._iopub_channel is None:
66 if self._iopub_channel is None:
66 self._iopub_channel = self.iopub_channel_class(self)
67 self._iopub_channel = self.iopub_channel_class(self)
67 return self._iopub_channel
68 return self._iopub_channel
68
69
69 @property
70 @property
70 def stdin_channel(self):
71 def stdin_channel(self):
71 if self._stdin_channel is None:
72 if self._stdin_channel is None:
72 self._stdin_channel = self.stdin_channel_class(self)
73 self._stdin_channel = self.stdin_channel_class(self)
73 return self._stdin_channel
74 return self._stdin_channel
74
75
75 @property
76 @property
76 def hb_channel(self):
77 def hb_channel(self):
77 if self._hb_channel is None:
78 if self._hb_channel is None:
78 self._hb_channel = self.hb_channel_class(self)
79 self._hb_channel = self.hb_channel_class(self)
79 return self._hb_channel
80 return self._hb_channel
80
81
81 # Methods for sending specific messages
82 # Methods for sending specific messages
82 # -------------------------------------
83 # -------------------------------------
83
84
84 def execute(self, code, silent=False, store_history=True,
85 def execute(self, code, silent=False, store_history=True,
85 user_expressions={}, allow_stdin=None):
86 user_expressions={}, allow_stdin=None):
86 if allow_stdin is None:
87 if allow_stdin is None:
87 allow_stdin = self.allow_stdin
88 allow_stdin = self.allow_stdin
88 content = dict(code=code, silent=silent, store_history=store_history,
89 content = dict(code=code, silent=silent, store_history=store_history,
89 user_expressions=user_expressions,
90 user_expressions=user_expressions,
90 allow_stdin=allow_stdin)
91 allow_stdin=allow_stdin)
91 msg = self.session.msg('execute_request', content)
92 msg = self.session.msg('execute_request', content)
92 self._dispatch_to_kernel(msg)
93 self._dispatch_to_kernel(msg)
93 return msg['header']['msg_id']
94 return msg['header']['msg_id']
94
95
95 def complete(self, code, cursor_pos=None):
96 def complete(self, code, cursor_pos=None):
96 if cursor_pos is None:
97 if cursor_pos is None:
97 cursor_pos = len(code)
98 cursor_pos = len(code)
98 content = dict(code=code, cursor_pos=cursor_pos)
99 content = dict(code=code, cursor_pos=cursor_pos)
99 msg = self.session.msg('complete_request', content)
100 msg = self.session.msg('complete_request', content)
100 self._dispatch_to_kernel(msg)
101 self._dispatch_to_kernel(msg)
101 return msg['header']['msg_id']
102 return msg['header']['msg_id']
102
103
103 def inspect(self, code, cursor_pos=None, detail_level=0):
104 def inspect(self, code, cursor_pos=None, detail_level=0):
104 if cursor_pos is None:
105 if cursor_pos is None:
105 cursor_pos = len(code)
106 cursor_pos = len(code)
106 content = dict(code=code, cursor_pos=cursor_pos,
107 content = dict(code=code, cursor_pos=cursor_pos,
107 detail_level=detail_level,
108 detail_level=detail_level,
108 )
109 )
109 msg = self.session.msg('inspect_request', content)
110 msg = self.session.msg('inspect_request', content)
110 self._dispatch_to_kernel(msg)
111 self._dispatch_to_kernel(msg)
111 return msg['header']['msg_id']
112 return msg['header']['msg_id']
112
113
113 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 content = dict(raw=raw, output=output,
115 content = dict(raw=raw, output=output,
115 hist_access_type=hist_access_type, **kwds)
116 hist_access_type=hist_access_type, **kwds)
116 msg = self.session.msg('history_request', content)
117 msg = self.session.msg('history_request', content)
117 self._dispatch_to_kernel(msg)
118 self._dispatch_to_kernel(msg)
118 return msg['header']['msg_id']
119 return msg['header']['msg_id']
119
120
120 def shutdown(self, restart=False):
121 def shutdown(self, restart=False):
121 # FIXME: What to do here?
122 # FIXME: What to do here?
122 raise NotImplementedError('Cannot shutdown in-process kernel')
123 raise NotImplementedError('Cannot shutdown in-process kernel')
123
124
124 def kernel_info(self):
125 def kernel_info(self):
125 """Request kernel info."""
126 """Request kernel info."""
126 msg = self.session.msg('kernel_info_request')
127 msg = self.session.msg('kernel_info_request')
127 self._dispatch_to_kernel(msg)
128 self._dispatch_to_kernel(msg)
128 return msg['header']['msg_id']
129 return msg['header']['msg_id']
129
130
130 def input(self, string):
131 def input(self, string):
131 if self.kernel is None:
132 if self.kernel is None:
132 raise RuntimeError('Cannot send input reply. No kernel exists.')
133 raise RuntimeError('Cannot send input reply. No kernel exists.')
133 self.kernel.raw_input_str = string
134 self.kernel.raw_input_str = string
134
135
135
136
136 def _dispatch_to_kernel(self, msg):
137 def _dispatch_to_kernel(self, msg):
137 """ Send a message to the kernel and handle a reply.
138 """ Send a message to the kernel and handle a reply.
138 """
139 """
139 kernel = self.kernel
140 kernel = self.kernel
140 if kernel is None:
141 if kernel is None:
141 raise RuntimeError('Cannot send request. No kernel exists.')
142 raise RuntimeError('Cannot send request. No kernel exists.')
142
143
143 stream = DummySocket()
144 stream = DummySocket()
144 self.session.send(stream, msg)
145 self.session.send(stream, msg)
145 msg_parts = stream.recv_multipart()
146 msg_parts = stream.recv_multipart()
146 kernel.dispatch_shell(stream, msg_parts)
147 kernel.dispatch_shell(stream, msg_parts)
147
148
148 idents, reply_msg = self.session.recv(stream, copy=False)
149 idents, reply_msg = self.session.recv(stream, copy=False)
149 self.shell_channel.call_handlers_later(reply_msg)
150 self.shell_channel.call_handlers_later(reply_msg)
150
151
151
152
152 #-----------------------------------------------------------------------------
153 #-----------------------------------------------------------------------------
153 # ABC Registration
154 # ABC Registration
154 #-----------------------------------------------------------------------------
155 #-----------------------------------------------------------------------------
155
156
156 KernelClientABC.register(InProcessKernelClient)
157 KernelClientABC.register(InProcessKernelClient)
@@ -1,169 +1,171 b''
1 """An in-process kernel"""
1 """An in-process kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from contextlib import contextmanager
6 from contextlib import contextmanager
7 import logging
7 import logging
8 import sys
8 import sys
9
9
10 from IPython.core.interactiveshell import InteractiveShellABC
10 from IPython.core.interactiveshell import InteractiveShellABC
11 from IPython.utils.jsonutil import json_clean
11 from IPython.utils.jsonutil import json_clean
12 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
12 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
13 from IPython.kernel.zmq.ipkernel import IPythonKernel
13 from IPython.kernel.zmq.ipkernel import IPythonKernel
14 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
14 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
15
15
16 from .socket import DummySocket
16 from .socket import DummySocket
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Main kernel class
19 # Main kernel class
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 class InProcessKernel(IPythonKernel):
22 class InProcessKernel(IPythonKernel):
23
23
24 #-------------------------------------------------------------------------
24 #-------------------------------------------------------------------------
25 # InProcessKernel interface
25 # InProcessKernel interface
26 #-------------------------------------------------------------------------
26 #-------------------------------------------------------------------------
27
27
28 # The frontends connected to this kernel.
28 # The frontends connected to this kernel.
29 frontends = List(
29 frontends = List(
30 Instance('IPython.kernel.inprocess.client.InProcessKernelClient')
30 Instance('IPython.kernel.inprocess.client.InProcessKernelClient'
31 allow_none=True)
31 )
32 )
32
33
33 # The GUI environment that the kernel is running under. This need not be
34 # The GUI environment that the kernel is running under. This need not be
34 # specified for the normal operation for the kernel, but is required for
35 # specified for the normal operation for the kernel, but is required for
35 # IPython's GUI support (including pylab). The default is 'inline' because
36 # IPython's GUI support (including pylab). The default is 'inline' because
36 # it is safe under all GUI toolkits.
37 # it is safe under all GUI toolkits.
37 gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'),
38 gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'),
38 default_value='inline')
39 default_value='inline')
39
40
40 raw_input_str = Any()
41 raw_input_str = Any()
41 stdout = Any()
42 stdout = Any()
42 stderr = Any()
43 stderr = Any()
43
44
44 #-------------------------------------------------------------------------
45 #-------------------------------------------------------------------------
45 # Kernel interface
46 # Kernel interface
46 #-------------------------------------------------------------------------
47 #-------------------------------------------------------------------------
47
48
48 shell_class = Type()
49 shell_class = Type()
49 shell_streams = List()
50 shell_streams = List()
50 control_stream = Any()
51 control_stream = Any()
51 iopub_socket = Instance(DummySocket, ())
52 iopub_socket = Instance(DummySocket, ())
52 stdin_socket = Instance(DummySocket, ())
53 stdin_socket = Instance(DummySocket, ())
53
54
54 def __init__(self, **traits):
55 def __init__(self, **traits):
55 super(InProcessKernel, self).__init__(**traits)
56 super(InProcessKernel, self).__init__(**traits)
56
57
57 self.iopub_socket.on_trait_change(self._io_dispatch, 'message_sent')
58 self.iopub_socket.on_trait_change(self._io_dispatch, 'message_sent')
58 self.shell.kernel = self
59 self.shell.kernel = self
59
60
60 def execute_request(self, stream, ident, parent):
61 def execute_request(self, stream, ident, parent):
61 """ Override for temporary IO redirection. """
62 """ Override for temporary IO redirection. """
62 with self._redirected_io():
63 with self._redirected_io():
63 super(InProcessKernel, self).execute_request(stream, ident, parent)
64 super(InProcessKernel, self).execute_request(stream, ident, parent)
64
65
65 def start(self):
66 def start(self):
66 """ Override registration of dispatchers for streams. """
67 """ Override registration of dispatchers for streams. """
67 self.shell.exit_now = False
68 self.shell.exit_now = False
68
69
69 def _abort_queue(self, stream):
70 def _abort_queue(self, stream):
70 """ The in-process kernel doesn't abort requests. """
71 """ The in-process kernel doesn't abort requests. """
71 pass
72 pass
72
73
73 def _input_request(self, prompt, ident, parent, password=False):
74 def _input_request(self, prompt, ident, parent, password=False):
74 # Flush output before making the request.
75 # Flush output before making the request.
75 self.raw_input_str = None
76 self.raw_input_str = None
76 sys.stderr.flush()
77 sys.stderr.flush()
77 sys.stdout.flush()
78 sys.stdout.flush()
78
79
79 # Send the input request.
80 # Send the input request.
80 content = json_clean(dict(prompt=prompt, password=password))
81 content = json_clean(dict(prompt=prompt, password=password))
81 msg = self.session.msg(u'input_request', content, parent)
82 msg = self.session.msg(u'input_request', content, parent)
82 for frontend in self.frontends:
83 for frontend in self.frontends:
83 if frontend.session.session == parent['header']['session']:
84 if frontend.session.session == parent['header']['session']:
84 frontend.stdin_channel.call_handlers(msg)
85 frontend.stdin_channel.call_handlers(msg)
85 break
86 break
86 else:
87 else:
87 logging.error('No frontend found for raw_input request')
88 logging.error('No frontend found for raw_input request')
88 return str()
89 return str()
89
90
90 # Await a response.
91 # Await a response.
91 while self.raw_input_str is None:
92 while self.raw_input_str is None:
92 frontend.stdin_channel.process_events()
93 frontend.stdin_channel.process_events()
93 return self.raw_input_str
94 return self.raw_input_str
94
95
95 #-------------------------------------------------------------------------
96 #-------------------------------------------------------------------------
96 # Protected interface
97 # Protected interface
97 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
98
99
99 @contextmanager
100 @contextmanager
100 def _redirected_io(self):
101 def _redirected_io(self):
101 """ Temporarily redirect IO to the kernel.
102 """ Temporarily redirect IO to the kernel.
102 """
103 """
103 sys_stdout, sys_stderr = sys.stdout, sys.stderr
104 sys_stdout, sys_stderr = sys.stdout, sys.stderr
104 sys.stdout, sys.stderr = self.stdout, self.stderr
105 sys.stdout, sys.stderr = self.stdout, self.stderr
105 yield
106 yield
106 sys.stdout, sys.stderr = sys_stdout, sys_stderr
107 sys.stdout, sys.stderr = sys_stdout, sys_stderr
107
108
108 #------ Trait change handlers --------------------------------------------
109 #------ Trait change handlers --------------------------------------------
109
110
110 def _io_dispatch(self):
111 def _io_dispatch(self):
111 """ Called when a message is sent to the IO socket.
112 """ Called when a message is sent to the IO socket.
112 """
113 """
113 ident, msg = self.session.recv(self.iopub_socket, copy=False)
114 ident, msg = self.session.recv(self.iopub_socket, copy=False)
114 for frontend in self.frontends:
115 for frontend in self.frontends:
115 frontend.iopub_channel.call_handlers(msg)
116 frontend.iopub_channel.call_handlers(msg)
116
117
117 #------ Trait initializers -----------------------------------------------
118 #------ Trait initializers -----------------------------------------------
118
119
119 def _log_default(self):
120 def _log_default(self):
120 return logging.getLogger(__name__)
121 return logging.getLogger(__name__)
121
122
122 def _session_default(self):
123 def _session_default(self):
123 from IPython.kernel.zmq.session import Session
124 from IPython.kernel.zmq.session import Session
124 return Session(parent=self, key=b'')
125 return Session(parent=self, key=b'')
125
126
126 def _shell_class_default(self):
127 def _shell_class_default(self):
127 return InProcessInteractiveShell
128 return InProcessInteractiveShell
128
129
129 def _stdout_default(self):
130 def _stdout_default(self):
130 from IPython.kernel.zmq.iostream import OutStream
131 from IPython.kernel.zmq.iostream import OutStream
131 return OutStream(self.session, self.iopub_socket, u'stdout', pipe=False)
132 return OutStream(self.session, self.iopub_socket, u'stdout', pipe=False)
132
133
133 def _stderr_default(self):
134 def _stderr_default(self):
134 from IPython.kernel.zmq.iostream import OutStream
135 from IPython.kernel.zmq.iostream import OutStream
135 return OutStream(self.session, self.iopub_socket, u'stderr', pipe=False)
136 return OutStream(self.session, self.iopub_socket, u'stderr', pipe=False)
136
137
137 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
138 # Interactive shell subclass
139 # Interactive shell subclass
139 #-----------------------------------------------------------------------------
140 #-----------------------------------------------------------------------------
140
141
141 class InProcessInteractiveShell(ZMQInteractiveShell):
142 class InProcessInteractiveShell(ZMQInteractiveShell):
142
143
143 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
144 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel',
145 allow_none=True)
144
146
145 #-------------------------------------------------------------------------
147 #-------------------------------------------------------------------------
146 # InteractiveShell interface
148 # InteractiveShell interface
147 #-------------------------------------------------------------------------
149 #-------------------------------------------------------------------------
148
150
149 def enable_gui(self, gui=None):
151 def enable_gui(self, gui=None):
150 """Enable GUI integration for the kernel."""
152 """Enable GUI integration for the kernel."""
151 from IPython.kernel.zmq.eventloops import enable_gui
153 from IPython.kernel.zmq.eventloops import enable_gui
152 if not gui:
154 if not gui:
153 gui = self.kernel.gui
155 gui = self.kernel.gui
154 return enable_gui(gui, kernel=self.kernel)
156 return enable_gui(gui, kernel=self.kernel)
155
157
156 def enable_matplotlib(self, gui=None):
158 def enable_matplotlib(self, gui=None):
157 """Enable matplotlib integration for the kernel."""
159 """Enable matplotlib integration for the kernel."""
158 if not gui:
160 if not gui:
159 gui = self.kernel.gui
161 gui = self.kernel.gui
160 return super(InProcessInteractiveShell, self).enable_matplotlib(gui)
162 return super(InProcessInteractiveShell, self).enable_matplotlib(gui)
161
163
162 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
164 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
163 """Activate pylab support at runtime."""
165 """Activate pylab support at runtime."""
164 if not gui:
166 if not gui:
165 gui = self.kernel.gui
167 gui = self.kernel.gui
166 return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all,
168 return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all,
167 welcome_message)
169 welcome_message)
168
170
169 InteractiveShellABC.register(InProcessInteractiveShell)
171 InteractiveShellABC.register(InProcessInteractiveShell)
@@ -1,71 +1,72 b''
1 """A kernel manager for in-process kernels."""
1 """A kernel manager for in-process kernels."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from IPython.utils.traitlets import Instance, DottedObjectName
6 from IPython.utils.traitlets import Instance, DottedObjectName
7 from IPython.kernel.managerabc import KernelManagerABC
7 from IPython.kernel.managerabc import KernelManagerABC
8 from IPython.kernel.manager import KernelManager
8 from IPython.kernel.manager import KernelManager
9 from IPython.kernel.zmq.session import Session
9 from IPython.kernel.zmq.session import Session
10
10
11
11
12 class InProcessKernelManager(KernelManager):
12 class InProcessKernelManager(KernelManager):
13 """A manager for an in-process kernel.
13 """A manager for an in-process kernel.
14
14
15 This class implements the interface of
15 This class implements the interface of
16 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
16 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
17 (asynchronous) frontends to be used seamlessly with an in-process kernel.
17 (asynchronous) frontends to be used seamlessly with an in-process kernel.
18
18
19 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
19 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
20 """
20 """
21
21
22 # The kernel process with which the KernelManager is communicating.
22 # The kernel process with which the KernelManager is communicating.
23 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
23 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel',
24 allow_none=True)
24 # the client class for KM.client() shortcut
25 # the client class for KM.client() shortcut
25 client_class = DottedObjectName('IPython.kernel.inprocess.BlockingInProcessKernelClient')
26 client_class = DottedObjectName('IPython.kernel.inprocess.BlockingInProcessKernelClient')
26
27
27 def _session_default(self):
28 def _session_default(self):
28 # don't sign in-process messages
29 # don't sign in-process messages
29 return Session(key=b'', parent=self)
30 return Session(key=b'', parent=self)
30
31
31 #--------------------------------------------------------------------------
32 #--------------------------------------------------------------------------
32 # Kernel management methods
33 # Kernel management methods
33 #--------------------------------------------------------------------------
34 #--------------------------------------------------------------------------
34
35
35 def start_kernel(self, **kwds):
36 def start_kernel(self, **kwds):
36 from IPython.kernel.inprocess.ipkernel import InProcessKernel
37 from IPython.kernel.inprocess.ipkernel import InProcessKernel
37 self.kernel = InProcessKernel(parent=self, session=self.session)
38 self.kernel = InProcessKernel(parent=self, session=self.session)
38
39
39 def shutdown_kernel(self):
40 def shutdown_kernel(self):
40 self._kill_kernel()
41 self._kill_kernel()
41
42
42 def restart_kernel(self, now=False, **kwds):
43 def restart_kernel(self, now=False, **kwds):
43 self.shutdown_kernel()
44 self.shutdown_kernel()
44 self.start_kernel(**kwds)
45 self.start_kernel(**kwds)
45
46
46 @property
47 @property
47 def has_kernel(self):
48 def has_kernel(self):
48 return self.kernel is not None
49 return self.kernel is not None
49
50
50 def _kill_kernel(self):
51 def _kill_kernel(self):
51 self.kernel = None
52 self.kernel = None
52
53
53 def interrupt_kernel(self):
54 def interrupt_kernel(self):
54 raise NotImplementedError("Cannot interrupt in-process kernel.")
55 raise NotImplementedError("Cannot interrupt in-process kernel.")
55
56
56 def signal_kernel(self, signum):
57 def signal_kernel(self, signum):
57 raise NotImplementedError("Cannot signal in-process kernel.")
58 raise NotImplementedError("Cannot signal in-process kernel.")
58
59
59 def is_alive(self):
60 def is_alive(self):
60 return self.kernel is not None
61 return self.kernel is not None
61
62
62 def client(self, **kwargs):
63 def client(self, **kwargs):
63 kwargs['kernel'] = self.kernel
64 kwargs['kernel'] = self.kernel
64 return super(InProcessKernelManager, self).client(**kwargs)
65 return super(InProcessKernelManager, self).client(**kwargs)
65
66
66
67
67 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
68 # ABC Registration
69 # ABC Registration
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70
71
71 KernelManagerABC.register(InProcessKernelManager)
72 KernelManagerABC.register(InProcessKernelManager)
@@ -1,62 +1,62 b''
1 """A kernel manager with a tornado IOLoop"""
1 """A kernel manager with a tornado IOLoop"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 from zmq.eventloop import ioloop
16 from zmq.eventloop import ioloop
17 from zmq.eventloop.zmqstream import ZMQStream
17 from zmq.eventloop.zmqstream import ZMQStream
18
18
19 from IPython.utils.traitlets import (
19 from IPython.utils.traitlets import (
20 Instance
20 Instance
21 )
21 )
22
22
23 from IPython.kernel.manager import KernelManager
23 from IPython.kernel.manager import KernelManager
24 from .restarter import IOLoopKernelRestarter
24 from .restarter import IOLoopKernelRestarter
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 def as_zmqstream(f):
31 def as_zmqstream(f):
32 def wrapped(self, *args, **kwargs):
32 def wrapped(self, *args, **kwargs):
33 socket = f(self, *args, **kwargs)
33 socket = f(self, *args, **kwargs)
34 return ZMQStream(socket, self.loop)
34 return ZMQStream(socket, self.loop)
35 return wrapped
35 return wrapped
36
36
37 class IOLoopKernelManager(KernelManager):
37 class IOLoopKernelManager(KernelManager):
38
38
39 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
39 loop = Instance('zmq.eventloop.ioloop.IOLoop')
40 def _loop_default(self):
40 def _loop_default(self):
41 return ioloop.IOLoop.instance()
41 return ioloop.IOLoop.instance()
42
42
43 _restarter = Instance('IPython.kernel.ioloop.IOLoopKernelRestarter')
43 _restarter = Instance('IPython.kernel.ioloop.IOLoopKernelRestarter', allow_none=True)
44
44
45 def start_restarter(self):
45 def start_restarter(self):
46 if self.autorestart and self.has_kernel:
46 if self.autorestart and self.has_kernel:
47 if self._restarter is None:
47 if self._restarter is None:
48 self._restarter = IOLoopKernelRestarter(
48 self._restarter = IOLoopKernelRestarter(
49 kernel_manager=self, loop=self.loop,
49 kernel_manager=self, loop=self.loop,
50 parent=self, log=self.log
50 parent=self, log=self.log
51 )
51 )
52 self._restarter.start()
52 self._restarter.start()
53
53
54 def stop_restarter(self):
54 def stop_restarter(self):
55 if self.autorestart:
55 if self.autorestart:
56 if self._restarter is not None:
56 if self._restarter is not None:
57 self._restarter.stop()
57 self._restarter.stop()
58
58
59 connect_shell = as_zmqstream(KernelManager.connect_shell)
59 connect_shell = as_zmqstream(KernelManager.connect_shell)
60 connect_iopub = as_zmqstream(KernelManager.connect_iopub)
60 connect_iopub = as_zmqstream(KernelManager.connect_iopub)
61 connect_stdin = as_zmqstream(KernelManager.connect_stdin)
61 connect_stdin = as_zmqstream(KernelManager.connect_stdin)
62 connect_hb = as_zmqstream(KernelManager.connect_hb)
62 connect_hb = as_zmqstream(KernelManager.connect_hb)
@@ -1,54 +1,54 b''
1 """A basic in process kernel monitor with autorestarting.
1 """A basic in process kernel monitor with autorestarting.
2
2
3 This watches a kernel's state using KernelManager.is_alive and auto
3 This watches a kernel's state using KernelManager.is_alive and auto
4 restarts the kernel if it dies.
4 restarts the kernel if it dies.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2013 The IPython Development Team
8 # Copyright (C) 2013 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 from zmq.eventloop import ioloop
20 from zmq.eventloop import ioloop
21
21
22
22
23 from IPython.kernel.restarter import KernelRestarter
23 from IPython.kernel.restarter import KernelRestarter
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import (
25 Instance,
25 Instance,
26 )
26 )
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Code
29 # Code
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class IOLoopKernelRestarter(KernelRestarter):
32 class IOLoopKernelRestarter(KernelRestarter):
33 """Monitor and autorestart a kernel."""
33 """Monitor and autorestart a kernel."""
34
34
35 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
35 loop = Instance('zmq.eventloop.ioloop.IOLoop')
36 def _loop_default(self):
36 def _loop_default(self):
37 return ioloop.IOLoop.instance()
37 return ioloop.IOLoop.instance()
38
38
39 _pcallback = None
39 _pcallback = None
40
40
41 def start(self):
41 def start(self):
42 """Start the polling of the kernel."""
42 """Start the polling of the kernel."""
43 if self._pcallback is None:
43 if self._pcallback is None:
44 self._pcallback = ioloop.PeriodicCallback(
44 self._pcallback = ioloop.PeriodicCallback(
45 self.poll, 1000*self.time_to_dead, self.loop
45 self.poll, 1000*self.time_to_dead, self.loop
46 )
46 )
47 self._pcallback.start()
47 self._pcallback.start()
48
48
49 def stop(self):
49 def stop(self):
50 """Stop the kernel polling."""
50 """Stop the kernel polling."""
51 if self._pcallback is not None:
51 if self._pcallback is not None:
52 self._pcallback.stop()
52 self._pcallback.stop()
53 self._pcallback = None
53 self._pcallback = None
54
54
@@ -1,70 +1,70 b''
1 """Publishing native (typically pickled) objects.
1 """Publishing native (typically pickled) objects.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2012 The IPython Development Team
5 # Copyright (C) 2012 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from IPython.config import Configurable
15 from IPython.config import Configurable
16 from IPython.kernel.inprocess.socket import SocketABC
16 from IPython.kernel.inprocess.socket import SocketABC
17 from IPython.utils.jsonutil import json_clean
17 from IPython.utils.jsonutil import json_clean
18 from IPython.utils.traitlets import Instance, Dict, CBytes
18 from IPython.utils.traitlets import Instance, Dict, CBytes
19 from IPython.kernel.zmq.serialize import serialize_object
19 from IPython.kernel.zmq.serialize import serialize_object
20 from IPython.kernel.zmq.session import Session, extract_header
20 from IPython.kernel.zmq.session import Session, extract_header
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Code
23 # Code
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26
26
27 class ZMQDataPublisher(Configurable):
27 class ZMQDataPublisher(Configurable):
28
28
29 topic = topic = CBytes(b'datapub')
29 topic = topic = CBytes(b'datapub')
30 session = Instance(Session)
30 session = Instance(Session, allow_none=True)
31 pub_socket = Instance(SocketABC)
31 pub_socket = Instance(SocketABC, allow_none=True)
32 parent_header = Dict({})
32 parent_header = Dict({})
33
33
34 def set_parent(self, parent):
34 def set_parent(self, parent):
35 """Set the parent for outbound messages."""
35 """Set the parent for outbound messages."""
36 self.parent_header = extract_header(parent)
36 self.parent_header = extract_header(parent)
37
37
38 def publish_data(self, data):
38 def publish_data(self, data):
39 """publish a data_message on the IOPub channel
39 """publish a data_message on the IOPub channel
40
40
41 Parameters
41 Parameters
42 ----------
42 ----------
43
43
44 data : dict
44 data : dict
45 The data to be published. Think of it as a namespace.
45 The data to be published. Think of it as a namespace.
46 """
46 """
47 session = self.session
47 session = self.session
48 buffers = serialize_object(data,
48 buffers = serialize_object(data,
49 buffer_threshold=session.buffer_threshold,
49 buffer_threshold=session.buffer_threshold,
50 item_threshold=session.item_threshold,
50 item_threshold=session.item_threshold,
51 )
51 )
52 content = json_clean(dict(keys=data.keys()))
52 content = json_clean(dict(keys=data.keys()))
53 session.send(self.pub_socket, 'data_message', content=content,
53 session.send(self.pub_socket, 'data_message', content=content,
54 parent=self.parent_header,
54 parent=self.parent_header,
55 buffers=buffers,
55 buffers=buffers,
56 ident=self.topic,
56 ident=self.topic,
57 )
57 )
58
58
59
59
60 def publish_data(data):
60 def publish_data(data):
61 """publish a data_message on the IOPub channel
61 """publish a data_message on the IOPub channel
62
62
63 Parameters
63 Parameters
64 ----------
64 ----------
65
65
66 data : dict
66 data : dict
67 The data to be published. Think of it as a namespace.
67 The data to be published. Think of it as a namespace.
68 """
68 """
69 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
69 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
70 ZMQInteractiveShell.instance().data_pub.publish_data(data)
70 ZMQInteractiveShell.instance().data_pub.publish_data(data)
@@ -1,74 +1,74 b''
1 """Replacements for sys.displayhook that publish over ZMQ."""
1 """Replacements for sys.displayhook that publish over ZMQ."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import sys
6 import sys
7
7
8 from IPython.core.displayhook import DisplayHook
8 from IPython.core.displayhook import DisplayHook
9 from IPython.kernel.inprocess.socket import SocketABC
9 from IPython.kernel.inprocess.socket import SocketABC
10 from IPython.utils.jsonutil import encode_images
10 from IPython.utils.jsonutil import encode_images
11 from IPython.utils.py3compat import builtin_mod
11 from IPython.utils.py3compat import builtin_mod
12 from IPython.utils.traitlets import Instance, Dict
12 from IPython.utils.traitlets import Instance, Dict
13 from .session import extract_header, Session
13 from .session import extract_header, Session
14
14
15 class ZMQDisplayHook(object):
15 class ZMQDisplayHook(object):
16 """A simple displayhook that publishes the object's repr over a ZeroMQ
16 """A simple displayhook that publishes the object's repr over a ZeroMQ
17 socket."""
17 socket."""
18 topic=b'execute_result'
18 topic=b'execute_result'
19
19
20 def __init__(self, session, pub_socket):
20 def __init__(self, session, pub_socket):
21 self.session = session
21 self.session = session
22 self.pub_socket = pub_socket
22 self.pub_socket = pub_socket
23 self.parent_header = {}
23 self.parent_header = {}
24
24
25 def __call__(self, obj):
25 def __call__(self, obj):
26 if obj is None:
26 if obj is None:
27 return
27 return
28
28
29 builtin_mod._ = obj
29 builtin_mod._ = obj
30 sys.stdout.flush()
30 sys.stdout.flush()
31 sys.stderr.flush()
31 sys.stderr.flush()
32 msg = self.session.send(self.pub_socket, u'execute_result', {u'data':repr(obj)},
32 msg = self.session.send(self.pub_socket, u'execute_result', {u'data':repr(obj)},
33 parent=self.parent_header, ident=self.topic)
33 parent=self.parent_header, ident=self.topic)
34
34
35 def set_parent(self, parent):
35 def set_parent(self, parent):
36 self.parent_header = extract_header(parent)
36 self.parent_header = extract_header(parent)
37
37
38
38
39 class ZMQShellDisplayHook(DisplayHook):
39 class ZMQShellDisplayHook(DisplayHook):
40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
41 to work with an InteractiveShell instance. It sends a dict of different
41 to work with an InteractiveShell instance. It sends a dict of different
42 representations of the object."""
42 representations of the object."""
43 topic=None
43 topic=None
44
44
45 session = Instance(Session)
45 session = Instance(Session, allow_none=True)
46 pub_socket = Instance(SocketABC)
46 pub_socket = Instance(SocketABC, allow_none=True)
47 parent_header = Dict({})
47 parent_header = Dict({})
48
48
49 def set_parent(self, parent):
49 def set_parent(self, parent):
50 """Set the parent for outbound messages."""
50 """Set the parent for outbound messages."""
51 self.parent_header = extract_header(parent)
51 self.parent_header = extract_header(parent)
52
52
53 def start_displayhook(self):
53 def start_displayhook(self):
54 self.msg = self.session.msg(u'execute_result', {
54 self.msg = self.session.msg(u'execute_result', {
55 'data': {},
55 'data': {},
56 'metadata': {},
56 'metadata': {},
57 }, parent=self.parent_header)
57 }, parent=self.parent_header)
58
58
59 def write_output_prompt(self):
59 def write_output_prompt(self):
60 """Write the output prompt."""
60 """Write the output prompt."""
61 self.msg['content']['execution_count'] = self.prompt_count
61 self.msg['content']['execution_count'] = self.prompt_count
62
62
63 def write_format_data(self, format_dict, md_dict=None):
63 def write_format_data(self, format_dict, md_dict=None):
64 self.msg['content']['data'] = encode_images(format_dict)
64 self.msg['content']['data'] = encode_images(format_dict)
65 self.msg['content']['metadata'] = md_dict
65 self.msg['content']['metadata'] = md_dict
66
66
67 def finish_displayhook(self):
67 def finish_displayhook(self):
68 """Finish up all displayhook activities."""
68 """Finish up all displayhook activities."""
69 sys.stdout.flush()
69 sys.stdout.flush()
70 sys.stderr.flush()
70 sys.stderr.flush()
71 if self.msg['content']['data']:
71 if self.msg['content']['data']:
72 self.session.send(self.pub_socket, self.msg, ident=self.topic)
72 self.session.send(self.pub_socket, self.msg, ident=self.topic)
73 self.msg = None
73 self.msg = None
74
74
@@ -1,367 +1,368 b''
1 """The IPython kernel implementation"""
1 """The IPython kernel implementation"""
2
2
3 import getpass
3 import getpass
4 import sys
4 import sys
5 import traceback
5 import traceback
6
6
7 from IPython.core import release
7 from IPython.core import release
8 from IPython.utils.py3compat import builtin_mod, PY3
8 from IPython.utils.py3compat import builtin_mod, PY3
9 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
10 from IPython.utils.traitlets import Instance, Type, Any, List
10 from IPython.utils.traitlets import Instance, Type, Any, List
11 from IPython.utils.decorators import undoc
11 from IPython.utils.decorators import undoc
12
12
13 from ..comm import CommManager
13 from ..comm import CommManager
14 from .kernelbase import Kernel as KernelBase
14 from .kernelbase import Kernel as KernelBase
15 from .serialize import serialize_object, unpack_apply_message
15 from .serialize import serialize_object, unpack_apply_message
16 from .zmqshell import ZMQInteractiveShell
16 from .zmqshell import ZMQInteractiveShell
17
17
18
18
19 def lazy_import_handle_comm_opened(*args, **kwargs):
19 def lazy_import_handle_comm_opened(*args, **kwargs):
20 from IPython.html.widgets import Widget
20 from IPython.html.widgets import Widget
21 Widget.handle_comm_opened(*args, **kwargs)
21 Widget.handle_comm_opened(*args, **kwargs)
22
22
23
23
24 class IPythonKernel(KernelBase):
24 class IPythonKernel(KernelBase):
25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
26 allow_none=True)
26 shell_class = Type(ZMQInteractiveShell)
27 shell_class = Type(ZMQInteractiveShell)
27
28
28 user_module = Any()
29 user_module = Any()
29 def _user_module_changed(self, name, old, new):
30 def _user_module_changed(self, name, old, new):
30 if self.shell is not None:
31 if self.shell is not None:
31 self.shell.user_module = new
32 self.shell.user_module = new
32
33
33 user_ns = Instance(dict, args=None, allow_none=True)
34 user_ns = Instance(dict, args=None, allow_none=True)
34 def _user_ns_changed(self, name, old, new):
35 def _user_ns_changed(self, name, old, new):
35 if self.shell is not None:
36 if self.shell is not None:
36 self.shell.user_ns = new
37 self.shell.user_ns = new
37 self.shell.init_user_ns()
38 self.shell.init_user_ns()
38
39
39 # A reference to the Python builtin 'raw_input' function.
40 # A reference to the Python builtin 'raw_input' function.
40 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
41 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
41 _sys_raw_input = Any()
42 _sys_raw_input = Any()
42 _sys_eval_input = Any()
43 _sys_eval_input = Any()
43
44
44 def __init__(self, **kwargs):
45 def __init__(self, **kwargs):
45 super(IPythonKernel, self).__init__(**kwargs)
46 super(IPythonKernel, self).__init__(**kwargs)
46
47
47 # Initialize the InteractiveShell subclass
48 # Initialize the InteractiveShell subclass
48 self.shell = self.shell_class.instance(parent=self,
49 self.shell = self.shell_class.instance(parent=self,
49 profile_dir = self.profile_dir,
50 profile_dir = self.profile_dir,
50 user_module = self.user_module,
51 user_module = self.user_module,
51 user_ns = self.user_ns,
52 user_ns = self.user_ns,
52 kernel = self,
53 kernel = self,
53 )
54 )
54 self.shell.displayhook.session = self.session
55 self.shell.displayhook.session = self.session
55 self.shell.displayhook.pub_socket = self.iopub_socket
56 self.shell.displayhook.pub_socket = self.iopub_socket
56 self.shell.displayhook.topic = self._topic('execute_result')
57 self.shell.displayhook.topic = self._topic('execute_result')
57 self.shell.display_pub.session = self.session
58 self.shell.display_pub.session = self.session
58 self.shell.display_pub.pub_socket = self.iopub_socket
59 self.shell.display_pub.pub_socket = self.iopub_socket
59 self.shell.data_pub.session = self.session
60 self.shell.data_pub.session = self.session
60 self.shell.data_pub.pub_socket = self.iopub_socket
61 self.shell.data_pub.pub_socket = self.iopub_socket
61
62
62 # TMP - hack while developing
63 # TMP - hack while developing
63 self.shell._reply_content = None
64 self.shell._reply_content = None
64
65
65 self.comm_manager = CommManager(shell=self.shell, parent=self,
66 self.comm_manager = CommManager(shell=self.shell, parent=self,
66 kernel=self)
67 kernel=self)
67 self.comm_manager.register_target('ipython.widget', lazy_import_handle_comm_opened)
68 self.comm_manager.register_target('ipython.widget', lazy_import_handle_comm_opened)
68
69
69 self.shell.configurables.append(self.comm_manager)
70 self.shell.configurables.append(self.comm_manager)
70 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
71 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
71 for msg_type in comm_msg_types:
72 for msg_type in comm_msg_types:
72 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
73 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
73
74
74 help_links = List([
75 help_links = List([
75 {
76 {
76 'text': "Python",
77 'text': "Python",
77 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2],
78 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2],
78 },
79 },
79 {
80 {
80 'text': "IPython",
81 'text': "IPython",
81 'url': "http://ipython.org/documentation.html",
82 'url': "http://ipython.org/documentation.html",
82 },
83 },
83 {
84 {
84 'text': "NumPy",
85 'text': "NumPy",
85 'url': "http://docs.scipy.org/doc/numpy/reference/",
86 'url': "http://docs.scipy.org/doc/numpy/reference/",
86 },
87 },
87 {
88 {
88 'text': "SciPy",
89 'text': "SciPy",
89 'url': "http://docs.scipy.org/doc/scipy/reference/",
90 'url': "http://docs.scipy.org/doc/scipy/reference/",
90 },
91 },
91 {
92 {
92 'text': "Matplotlib",
93 'text': "Matplotlib",
93 'url': "http://matplotlib.org/contents.html",
94 'url': "http://matplotlib.org/contents.html",
94 },
95 },
95 {
96 {
96 'text': "SymPy",
97 'text': "SymPy",
97 'url': "http://docs.sympy.org/latest/index.html",
98 'url': "http://docs.sympy.org/latest/index.html",
98 },
99 },
99 {
100 {
100 'text': "pandas",
101 'text': "pandas",
101 'url': "http://pandas.pydata.org/pandas-docs/stable/",
102 'url': "http://pandas.pydata.org/pandas-docs/stable/",
102 },
103 },
103 ])
104 ])
104
105
105 # Kernel info fields
106 # Kernel info fields
106 implementation = 'ipython'
107 implementation = 'ipython'
107 implementation_version = release.version
108 implementation_version = release.version
108 language_info = {
109 language_info = {
109 'name': 'python',
110 'name': 'python',
110 'version': sys.version.split()[0],
111 'version': sys.version.split()[0],
111 'mimetype': 'text/x-python',
112 'mimetype': 'text/x-python',
112 'codemirror_mode': {'name': 'ipython',
113 'codemirror_mode': {'name': 'ipython',
113 'version': sys.version_info[0]},
114 'version': sys.version_info[0]},
114 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
115 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
115 'nbconvert_exporter': 'python',
116 'nbconvert_exporter': 'python',
116 'file_extension': '.py'
117 'file_extension': '.py'
117 }
118 }
118 @property
119 @property
119 def banner(self):
120 def banner(self):
120 return self.shell.banner
121 return self.shell.banner
121
122
122 def start(self):
123 def start(self):
123 self.shell.exit_now = False
124 self.shell.exit_now = False
124 super(IPythonKernel, self).start()
125 super(IPythonKernel, self).start()
125
126
126 def set_parent(self, ident, parent):
127 def set_parent(self, ident, parent):
127 """Overridden from parent to tell the display hook and output streams
128 """Overridden from parent to tell the display hook and output streams
128 about the parent message.
129 about the parent message.
129 """
130 """
130 super(IPythonKernel, self).set_parent(ident, parent)
131 super(IPythonKernel, self).set_parent(ident, parent)
131 self.shell.set_parent(parent)
132 self.shell.set_parent(parent)
132
133
133 def _forward_input(self, allow_stdin=False):
134 def _forward_input(self, allow_stdin=False):
134 """Forward raw_input and getpass to the current frontend.
135 """Forward raw_input and getpass to the current frontend.
135
136
136 via input_request
137 via input_request
137 """
138 """
138 self._allow_stdin = allow_stdin
139 self._allow_stdin = allow_stdin
139
140
140 if PY3:
141 if PY3:
141 self._sys_raw_input = builtin_mod.input
142 self._sys_raw_input = builtin_mod.input
142 builtin_mod.input = self.raw_input
143 builtin_mod.input = self.raw_input
143 else:
144 else:
144 self._sys_raw_input = builtin_mod.raw_input
145 self._sys_raw_input = builtin_mod.raw_input
145 self._sys_eval_input = builtin_mod.input
146 self._sys_eval_input = builtin_mod.input
146 builtin_mod.raw_input = self.raw_input
147 builtin_mod.raw_input = self.raw_input
147 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
148 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
148 self._save_getpass = getpass.getpass
149 self._save_getpass = getpass.getpass
149 getpass.getpass = self.getpass
150 getpass.getpass = self.getpass
150
151
151 def _restore_input(self):
152 def _restore_input(self):
152 """Restore raw_input, getpass"""
153 """Restore raw_input, getpass"""
153 if PY3:
154 if PY3:
154 builtin_mod.input = self._sys_raw_input
155 builtin_mod.input = self._sys_raw_input
155 else:
156 else:
156 builtin_mod.raw_input = self._sys_raw_input
157 builtin_mod.raw_input = self._sys_raw_input
157 builtin_mod.input = self._sys_eval_input
158 builtin_mod.input = self._sys_eval_input
158
159
159 getpass.getpass = self._save_getpass
160 getpass.getpass = self._save_getpass
160
161
161 @property
162 @property
162 def execution_count(self):
163 def execution_count(self):
163 return self.shell.execution_count
164 return self.shell.execution_count
164
165
165 @execution_count.setter
166 @execution_count.setter
166 def execution_count(self, value):
167 def execution_count(self, value):
167 # Ignore the incrememnting done by KernelBase, in favour of our shell's
168 # Ignore the incrememnting done by KernelBase, in favour of our shell's
168 # execution counter.
169 # execution counter.
169 pass
170 pass
170
171
171 def do_execute(self, code, silent, store_history=True,
172 def do_execute(self, code, silent, store_history=True,
172 user_expressions=None, allow_stdin=False):
173 user_expressions=None, allow_stdin=False):
173 shell = self.shell # we'll need this a lot here
174 shell = self.shell # we'll need this a lot here
174
175
175 self._forward_input(allow_stdin)
176 self._forward_input(allow_stdin)
176
177
177 reply_content = {}
178 reply_content = {}
178 # FIXME: the shell calls the exception handler itself.
179 # FIXME: the shell calls the exception handler itself.
179 shell._reply_content = None
180 shell._reply_content = None
180 try:
181 try:
181 shell.run_cell(code, store_history=store_history, silent=silent)
182 shell.run_cell(code, store_history=store_history, silent=silent)
182 except:
183 except:
183 status = u'error'
184 status = u'error'
184 # FIXME: this code right now isn't being used yet by default,
185 # FIXME: this code right now isn't being used yet by default,
185 # because the run_cell() call above directly fires off exception
186 # because the run_cell() call above directly fires off exception
186 # reporting. This code, therefore, is only active in the scenario
187 # reporting. This code, therefore, is only active in the scenario
187 # where runlines itself has an unhandled exception. We need to
188 # where runlines itself has an unhandled exception. We need to
188 # uniformize this, for all exception construction to come from a
189 # uniformize this, for all exception construction to come from a
189 # single location in the codbase.
190 # single location in the codbase.
190 etype, evalue, tb = sys.exc_info()
191 etype, evalue, tb = sys.exc_info()
191 tb_list = traceback.format_exception(etype, evalue, tb)
192 tb_list = traceback.format_exception(etype, evalue, tb)
192 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
193 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
193 else:
194 else:
194 status = u'ok'
195 status = u'ok'
195 finally:
196 finally:
196 self._restore_input()
197 self._restore_input()
197
198
198 reply_content[u'status'] = status
199 reply_content[u'status'] = status
199
200
200 # Return the execution counter so clients can display prompts
201 # Return the execution counter so clients can display prompts
201 reply_content['execution_count'] = shell.execution_count - 1
202 reply_content['execution_count'] = shell.execution_count - 1
202
203
203 # FIXME - fish exception info out of shell, possibly left there by
204 # FIXME - fish exception info out of shell, possibly left there by
204 # runlines. We'll need to clean up this logic later.
205 # runlines. We'll need to clean up this logic later.
205 if shell._reply_content is not None:
206 if shell._reply_content is not None:
206 reply_content.update(shell._reply_content)
207 reply_content.update(shell._reply_content)
207 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
208 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
208 reply_content['engine_info'] = e_info
209 reply_content['engine_info'] = e_info
209 # reset after use
210 # reset after use
210 shell._reply_content = None
211 shell._reply_content = None
211
212
212 if 'traceback' in reply_content:
213 if 'traceback' in reply_content:
213 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
214 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
214
215
215
216
216 # At this point, we can tell whether the main code execution succeeded
217 # At this point, we can tell whether the main code execution succeeded
217 # or not. If it did, we proceed to evaluate user_expressions
218 # or not. If it did, we proceed to evaluate user_expressions
218 if reply_content['status'] == 'ok':
219 if reply_content['status'] == 'ok':
219 reply_content[u'user_expressions'] = \
220 reply_content[u'user_expressions'] = \
220 shell.user_expressions(user_expressions or {})
221 shell.user_expressions(user_expressions or {})
221 else:
222 else:
222 # If there was an error, don't even try to compute expressions
223 # If there was an error, don't even try to compute expressions
223 reply_content[u'user_expressions'] = {}
224 reply_content[u'user_expressions'] = {}
224
225
225 # Payloads should be retrieved regardless of outcome, so we can both
226 # Payloads should be retrieved regardless of outcome, so we can both
226 # recover partial output (that could have been generated early in a
227 # recover partial output (that could have been generated early in a
227 # block, before an error) and clear the payload system always.
228 # block, before an error) and clear the payload system always.
228 reply_content[u'payload'] = shell.payload_manager.read_payload()
229 reply_content[u'payload'] = shell.payload_manager.read_payload()
229 # Be agressive about clearing the payload because we don't want
230 # Be agressive about clearing the payload because we don't want
230 # it to sit in memory until the next execute_request comes in.
231 # it to sit in memory until the next execute_request comes in.
231 shell.payload_manager.clear_payload()
232 shell.payload_manager.clear_payload()
232
233
233 return reply_content
234 return reply_content
234
235
235 def do_complete(self, code, cursor_pos):
236 def do_complete(self, code, cursor_pos):
236 # FIXME: IPython completers currently assume single line,
237 # FIXME: IPython completers currently assume single line,
237 # but completion messages give multi-line context
238 # but completion messages give multi-line context
238 # For now, extract line from cell, based on cursor_pos:
239 # For now, extract line from cell, based on cursor_pos:
239 if cursor_pos is None:
240 if cursor_pos is None:
240 cursor_pos = len(code)
241 cursor_pos = len(code)
241 line, offset = line_at_cursor(code, cursor_pos)
242 line, offset = line_at_cursor(code, cursor_pos)
242 line_cursor = cursor_pos - offset
243 line_cursor = cursor_pos - offset
243
244
244 txt, matches = self.shell.complete('', line, line_cursor)
245 txt, matches = self.shell.complete('', line, line_cursor)
245 return {'matches' : matches,
246 return {'matches' : matches,
246 'cursor_end' : cursor_pos,
247 'cursor_end' : cursor_pos,
247 'cursor_start' : cursor_pos - len(txt),
248 'cursor_start' : cursor_pos - len(txt),
248 'metadata' : {},
249 'metadata' : {},
249 'status' : 'ok'}
250 'status' : 'ok'}
250
251
251 def do_inspect(self, code, cursor_pos, detail_level=0):
252 def do_inspect(self, code, cursor_pos, detail_level=0):
252 name = token_at_cursor(code, cursor_pos)
253 name = token_at_cursor(code, cursor_pos)
253 info = self.shell.object_inspect(name)
254 info = self.shell.object_inspect(name)
254
255
255 reply_content = {'status' : 'ok'}
256 reply_content = {'status' : 'ok'}
256 reply_content['data'] = data = {}
257 reply_content['data'] = data = {}
257 reply_content['metadata'] = {}
258 reply_content['metadata'] = {}
258 reply_content['found'] = info['found']
259 reply_content['found'] = info['found']
259 if info['found']:
260 if info['found']:
260 info_text = self.shell.object_inspect_text(
261 info_text = self.shell.object_inspect_text(
261 name,
262 name,
262 detail_level=detail_level,
263 detail_level=detail_level,
263 )
264 )
264 data['text/plain'] = info_text
265 data['text/plain'] = info_text
265
266
266 return reply_content
267 return reply_content
267
268
268 def do_history(self, hist_access_type, output, raw, session=None, start=None,
269 def do_history(self, hist_access_type, output, raw, session=None, start=None,
269 stop=None, n=None, pattern=None, unique=False):
270 stop=None, n=None, pattern=None, unique=False):
270 if hist_access_type == 'tail':
271 if hist_access_type == 'tail':
271 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
272 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
272 include_latest=True)
273 include_latest=True)
273
274
274 elif hist_access_type == 'range':
275 elif hist_access_type == 'range':
275 hist = self.shell.history_manager.get_range(session, start, stop,
276 hist = self.shell.history_manager.get_range(session, start, stop,
276 raw=raw, output=output)
277 raw=raw, output=output)
277
278
278 elif hist_access_type == 'search':
279 elif hist_access_type == 'search':
279 hist = self.shell.history_manager.search(
280 hist = self.shell.history_manager.search(
280 pattern, raw=raw, output=output, n=n, unique=unique)
281 pattern, raw=raw, output=output, n=n, unique=unique)
281 else:
282 else:
282 hist = []
283 hist = []
283
284
284 return {'history' : list(hist)}
285 return {'history' : list(hist)}
285
286
286 def do_shutdown(self, restart):
287 def do_shutdown(self, restart):
287 self.shell.exit_now = True
288 self.shell.exit_now = True
288 return dict(status='ok', restart=restart)
289 return dict(status='ok', restart=restart)
289
290
290 def do_is_complete(self, code):
291 def do_is_complete(self, code):
291 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
292 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
292 r = {'status': status}
293 r = {'status': status}
293 if status == 'incomplete':
294 if status == 'incomplete':
294 r['indent'] = ' ' * indent_spaces
295 r['indent'] = ' ' * indent_spaces
295 return r
296 return r
296
297
297 def do_apply(self, content, bufs, msg_id, reply_metadata):
298 def do_apply(self, content, bufs, msg_id, reply_metadata):
298 shell = self.shell
299 shell = self.shell
299 try:
300 try:
300 working = shell.user_ns
301 working = shell.user_ns
301
302
302 prefix = "_"+str(msg_id).replace("-","")+"_"
303 prefix = "_"+str(msg_id).replace("-","")+"_"
303
304
304 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
305 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
305
306
306 fname = getattr(f, '__name__', 'f')
307 fname = getattr(f, '__name__', 'f')
307
308
308 fname = prefix+"f"
309 fname = prefix+"f"
309 argname = prefix+"args"
310 argname = prefix+"args"
310 kwargname = prefix+"kwargs"
311 kwargname = prefix+"kwargs"
311 resultname = prefix+"result"
312 resultname = prefix+"result"
312
313
313 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
314 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
314 # print ns
315 # print ns
315 working.update(ns)
316 working.update(ns)
316 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
317 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
317 try:
318 try:
318 exec(code, shell.user_global_ns, shell.user_ns)
319 exec(code, shell.user_global_ns, shell.user_ns)
319 result = working.get(resultname)
320 result = working.get(resultname)
320 finally:
321 finally:
321 for key in ns:
322 for key in ns:
322 working.pop(key)
323 working.pop(key)
323
324
324 result_buf = serialize_object(result,
325 result_buf = serialize_object(result,
325 buffer_threshold=self.session.buffer_threshold,
326 buffer_threshold=self.session.buffer_threshold,
326 item_threshold=self.session.item_threshold,
327 item_threshold=self.session.item_threshold,
327 )
328 )
328
329
329 except:
330 except:
330 # invoke IPython traceback formatting
331 # invoke IPython traceback formatting
331 shell.showtraceback()
332 shell.showtraceback()
332 # FIXME - fish exception info out of shell, possibly left there by
333 # FIXME - fish exception info out of shell, possibly left there by
333 # run_code. We'll need to clean up this logic later.
334 # run_code. We'll need to clean up this logic later.
334 reply_content = {}
335 reply_content = {}
335 if shell._reply_content is not None:
336 if shell._reply_content is not None:
336 reply_content.update(shell._reply_content)
337 reply_content.update(shell._reply_content)
337 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
338 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
338 reply_content['engine_info'] = e_info
339 reply_content['engine_info'] = e_info
339 # reset after use
340 # reset after use
340 shell._reply_content = None
341 shell._reply_content = None
341
342
342 self.send_response(self.iopub_socket, u'error', reply_content,
343 self.send_response(self.iopub_socket, u'error', reply_content,
343 ident=self._topic('error'))
344 ident=self._topic('error'))
344 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
345 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
345 result_buf = []
346 result_buf = []
346
347
347 if reply_content['ename'] == 'UnmetDependency':
348 if reply_content['ename'] == 'UnmetDependency':
348 reply_metadata['dependencies_met'] = False
349 reply_metadata['dependencies_met'] = False
349 else:
350 else:
350 reply_content = {'status' : 'ok'}
351 reply_content = {'status' : 'ok'}
351
352
352 return reply_content, result_buf
353 return reply_content, result_buf
353
354
354 def do_clear(self):
355 def do_clear(self):
355 self.shell.reset(False)
356 self.shell.reset(False)
356 return dict(status='ok')
357 return dict(status='ok')
357
358
358
359
359 # This exists only for backwards compatibility - use IPythonKernel instead
360 # This exists only for backwards compatibility - use IPythonKernel instead
360
361
361 @undoc
362 @undoc
362 class Kernel(IPythonKernel):
363 class Kernel(IPythonKernel):
363 def __init__(self, *args, **kwargs):
364 def __init__(self, *args, **kwargs):
364 import warnings
365 import warnings
365 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
366 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
366 DeprecationWarning)
367 DeprecationWarning)
367 super(Kernel, self).__init__(*args, **kwargs) No newline at end of file
368 super(Kernel, self).__init__(*args, **kwargs)
@@ -1,387 +1,387 b''
1 """An Application for launching a kernel"""
1 """An Application for launching a kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import atexit
8 import atexit
9 import os
9 import os
10 import sys
10 import sys
11 import signal
11 import signal
12
12
13 import zmq
13 import zmq
14 from zmq.eventloop import ioloop
14 from zmq.eventloop import ioloop
15 from zmq.eventloop.zmqstream import ZMQStream
15 from zmq.eventloop.zmqstream import ZMQStream
16
16
17 from IPython.core.ultratb import FormattedTB
17 from IPython.core.ultratb import FormattedTB
18 from IPython.core.application import (
18 from IPython.core.application import (
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
20 )
20 )
21 from IPython.core.profiledir import ProfileDir
21 from IPython.core.profiledir import ProfileDir
22 from IPython.core.shellapp import (
22 from IPython.core.shellapp import (
23 InteractiveShellApp, shell_flags, shell_aliases
23 InteractiveShellApp, shell_flags, shell_aliases
24 )
24 )
25 from IPython.utils import io
25 from IPython.utils import io
26 from IPython.utils.path import filefind
26 from IPython.utils.path import filefind
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
29 )
29 )
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.kernel import write_connection_file
31 from IPython.kernel import write_connection_file
32 from IPython.kernel.connect import ConnectionFileMixin
32 from IPython.kernel.connect import ConnectionFileMixin
33
33
34 # local imports
34 # local imports
35 from .heartbeat import Heartbeat
35 from .heartbeat import Heartbeat
36 from .ipkernel import IPythonKernel
36 from .ipkernel import IPythonKernel
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
38 from .session import (
38 from .session import (
39 Session, session_flags, session_aliases,
39 Session, session_flags, session_aliases,
40 )
40 )
41 from .zmqshell import ZMQInteractiveShell
41 from .zmqshell import ZMQInteractiveShell
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Flags and Aliases
44 # Flags and Aliases
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 kernel_aliases = dict(base_aliases)
47 kernel_aliases = dict(base_aliases)
48 kernel_aliases.update({
48 kernel_aliases.update({
49 'ip' : 'IPKernelApp.ip',
49 'ip' : 'IPKernelApp.ip',
50 'hb' : 'IPKernelApp.hb_port',
50 'hb' : 'IPKernelApp.hb_port',
51 'shell' : 'IPKernelApp.shell_port',
51 'shell' : 'IPKernelApp.shell_port',
52 'iopub' : 'IPKernelApp.iopub_port',
52 'iopub' : 'IPKernelApp.iopub_port',
53 'stdin' : 'IPKernelApp.stdin_port',
53 'stdin' : 'IPKernelApp.stdin_port',
54 'control' : 'IPKernelApp.control_port',
54 'control' : 'IPKernelApp.control_port',
55 'f' : 'IPKernelApp.connection_file',
55 'f' : 'IPKernelApp.connection_file',
56 'transport': 'IPKernelApp.transport',
56 'transport': 'IPKernelApp.transport',
57 })
57 })
58
58
59 kernel_flags = dict(base_flags)
59 kernel_flags = dict(base_flags)
60 kernel_flags.update({
60 kernel_flags.update({
61 'no-stdout' : (
61 'no-stdout' : (
62 {'IPKernelApp' : {'no_stdout' : True}},
62 {'IPKernelApp' : {'no_stdout' : True}},
63 "redirect stdout to the null device"),
63 "redirect stdout to the null device"),
64 'no-stderr' : (
64 'no-stderr' : (
65 {'IPKernelApp' : {'no_stderr' : True}},
65 {'IPKernelApp' : {'no_stderr' : True}},
66 "redirect stderr to the null device"),
66 "redirect stderr to the null device"),
67 'pylab' : (
67 'pylab' : (
68 {'IPKernelApp' : {'pylab' : 'auto'}},
68 {'IPKernelApp' : {'pylab' : 'auto'}},
69 """Pre-load matplotlib and numpy for interactive use with
69 """Pre-load matplotlib and numpy for interactive use with
70 the default matplotlib backend."""),
70 the default matplotlib backend."""),
71 })
71 })
72
72
73 # inherit flags&aliases for any IPython shell apps
73 # inherit flags&aliases for any IPython shell apps
74 kernel_aliases.update(shell_aliases)
74 kernel_aliases.update(shell_aliases)
75 kernel_flags.update(shell_flags)
75 kernel_flags.update(shell_flags)
76
76
77 # inherit flags&aliases for Sessions
77 # inherit flags&aliases for Sessions
78 kernel_aliases.update(session_aliases)
78 kernel_aliases.update(session_aliases)
79 kernel_flags.update(session_flags)
79 kernel_flags.update(session_flags)
80
80
81 _ctrl_c_message = """\
81 _ctrl_c_message = """\
82 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
82 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
83
83
84 To exit, you will have to explicitly quit this process, by either sending
84 To exit, you will have to explicitly quit this process, by either sending
85 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
85 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
86
86
87 To read more about this, see https://github.com/ipython/ipython/issues/2049
87 To read more about this, see https://github.com/ipython/ipython/issues/2049
88
88
89 """
89 """
90
90
91 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
92 # Application class for starting an IPython Kernel
92 # Application class for starting an IPython Kernel
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94
94
95 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
95 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
96 ConnectionFileMixin):
96 ConnectionFileMixin):
97 name='ipython-kernel'
97 name='ipython-kernel'
98 aliases = Dict(kernel_aliases)
98 aliases = Dict(kernel_aliases)
99 flags = Dict(kernel_flags)
99 flags = Dict(kernel_flags)
100 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
100 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
101 # the kernel class, as an importstring
101 # the kernel class, as an importstring
102 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
102 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
103 klass='IPython.kernel.zmq.kernelbase.Kernel',
103 klass='IPython.kernel.zmq.kernelbase.Kernel',
104 help="""The Kernel subclass to be used.
104 help="""The Kernel subclass to be used.
105
105
106 This should allow easy re-use of the IPKernelApp entry point
106 This should allow easy re-use of the IPKernelApp entry point
107 to configure and launch kernels other than IPython's own.
107 to configure and launch kernels other than IPython's own.
108 """)
108 """)
109 kernel = Any()
109 kernel = Any()
110 poller = Any() # don't restrict this even though current pollers are all Threads
110 poller = Any() # don't restrict this even though current pollers are all Threads
111 heartbeat = Instance(Heartbeat)
111 heartbeat = Instance(Heartbeat, allow_none=True)
112 ports = Dict()
112 ports = Dict()
113
113
114 # connection info:
114 # connection info:
115
115
116 @property
116 @property
117 def abs_connection_file(self):
117 def abs_connection_file(self):
118 if os.path.basename(self.connection_file) == self.connection_file:
118 if os.path.basename(self.connection_file) == self.connection_file:
119 return os.path.join(self.profile_dir.security_dir, self.connection_file)
119 return os.path.join(self.profile_dir.security_dir, self.connection_file)
120 else:
120 else:
121 return self.connection_file
121 return self.connection_file
122
122
123
123
124 # streams, etc.
124 # streams, etc.
125 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
125 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
126 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
126 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
127 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
127 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
128 config=True, help="The importstring for the OutStream factory")
128 config=True, help="The importstring for the OutStream factory")
129 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
129 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
130 config=True, help="The importstring for the DisplayHook factory")
130 config=True, help="The importstring for the DisplayHook factory")
131
131
132 # polling
132 # polling
133 parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), config=True,
133 parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), config=True,
134 help="""kill this process if its parent dies. On Windows, the argument
134 help="""kill this process if its parent dies. On Windows, the argument
135 specifies the HANDLE of the parent process, otherwise it is simply boolean.
135 specifies the HANDLE of the parent process, otherwise it is simply boolean.
136 """)
136 """)
137 interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), config=True,
137 interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), config=True,
138 help="""ONLY USED ON WINDOWS
138 help="""ONLY USED ON WINDOWS
139 Interrupt this process when the parent is signaled.
139 Interrupt this process when the parent is signaled.
140 """)
140 """)
141
141
142 def init_crash_handler(self):
142 def init_crash_handler(self):
143 # Install minimal exception handling
143 # Install minimal exception handling
144 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
144 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
145 ostream=sys.__stdout__)
145 ostream=sys.__stdout__)
146
146
147 def init_poller(self):
147 def init_poller(self):
148 if sys.platform == 'win32':
148 if sys.platform == 'win32':
149 if self.interrupt or self.parent_handle:
149 if self.interrupt or self.parent_handle:
150 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
150 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
151 elif self.parent_handle:
151 elif self.parent_handle:
152 self.poller = ParentPollerUnix()
152 self.poller = ParentPollerUnix()
153
153
154 def _bind_socket(self, s, port):
154 def _bind_socket(self, s, port):
155 iface = '%s://%s' % (self.transport, self.ip)
155 iface = '%s://%s' % (self.transport, self.ip)
156 if self.transport == 'tcp':
156 if self.transport == 'tcp':
157 if port <= 0:
157 if port <= 0:
158 port = s.bind_to_random_port(iface)
158 port = s.bind_to_random_port(iface)
159 else:
159 else:
160 s.bind("tcp://%s:%i" % (self.ip, port))
160 s.bind("tcp://%s:%i" % (self.ip, port))
161 elif self.transport == 'ipc':
161 elif self.transport == 'ipc':
162 if port <= 0:
162 if port <= 0:
163 port = 1
163 port = 1
164 path = "%s-%i" % (self.ip, port)
164 path = "%s-%i" % (self.ip, port)
165 while os.path.exists(path):
165 while os.path.exists(path):
166 port = port + 1
166 port = port + 1
167 path = "%s-%i" % (self.ip, port)
167 path = "%s-%i" % (self.ip, port)
168 else:
168 else:
169 path = "%s-%i" % (self.ip, port)
169 path = "%s-%i" % (self.ip, port)
170 s.bind("ipc://%s" % path)
170 s.bind("ipc://%s" % path)
171 return port
171 return port
172
172
173 def write_connection_file(self):
173 def write_connection_file(self):
174 """write connection info to JSON file"""
174 """write connection info to JSON file"""
175 cf = self.abs_connection_file
175 cf = self.abs_connection_file
176 self.log.debug("Writing connection file: %s", cf)
176 self.log.debug("Writing connection file: %s", cf)
177 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
177 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
178 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
178 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
179 iopub_port=self.iopub_port, control_port=self.control_port)
179 iopub_port=self.iopub_port, control_port=self.control_port)
180
180
181 def cleanup_connection_file(self):
181 def cleanup_connection_file(self):
182 cf = self.abs_connection_file
182 cf = self.abs_connection_file
183 self.log.debug("Cleaning up connection file: %s", cf)
183 self.log.debug("Cleaning up connection file: %s", cf)
184 try:
184 try:
185 os.remove(cf)
185 os.remove(cf)
186 except (IOError, OSError):
186 except (IOError, OSError):
187 pass
187 pass
188
188
189 self.cleanup_ipc_files()
189 self.cleanup_ipc_files()
190
190
191 def init_connection_file(self):
191 def init_connection_file(self):
192 if not self.connection_file:
192 if not self.connection_file:
193 self.connection_file = "kernel-%s.json"%os.getpid()
193 self.connection_file = "kernel-%s.json"%os.getpid()
194 try:
194 try:
195 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
195 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
196 except IOError:
196 except IOError:
197 self.log.debug("Connection file not found: %s", self.connection_file)
197 self.log.debug("Connection file not found: %s", self.connection_file)
198 # This means I own it, so I will clean it up:
198 # This means I own it, so I will clean it up:
199 atexit.register(self.cleanup_connection_file)
199 atexit.register(self.cleanup_connection_file)
200 return
200 return
201 try:
201 try:
202 self.load_connection_file()
202 self.load_connection_file()
203 except Exception:
203 except Exception:
204 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
204 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
205 self.exit(1)
205 self.exit(1)
206
206
207 def init_sockets(self):
207 def init_sockets(self):
208 # Create a context, a session, and the kernel sockets.
208 # Create a context, a session, and the kernel sockets.
209 self.log.info("Starting the kernel at pid: %i", os.getpid())
209 self.log.info("Starting the kernel at pid: %i", os.getpid())
210 context = zmq.Context.instance()
210 context = zmq.Context.instance()
211 # Uncomment this to try closing the context.
211 # Uncomment this to try closing the context.
212 # atexit.register(context.term)
212 # atexit.register(context.term)
213
213
214 self.shell_socket = context.socket(zmq.ROUTER)
214 self.shell_socket = context.socket(zmq.ROUTER)
215 self.shell_socket.linger = 1000
215 self.shell_socket.linger = 1000
216 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
216 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
217 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
217 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
218
218
219 self.iopub_socket = context.socket(zmq.PUB)
219 self.iopub_socket = context.socket(zmq.PUB)
220 self.iopub_socket.linger = 1000
220 self.iopub_socket.linger = 1000
221 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
221 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
222 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
222 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
223
223
224 self.stdin_socket = context.socket(zmq.ROUTER)
224 self.stdin_socket = context.socket(zmq.ROUTER)
225 self.stdin_socket.linger = 1000
225 self.stdin_socket.linger = 1000
226 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
226 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
227 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
227 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
228
228
229 self.control_socket = context.socket(zmq.ROUTER)
229 self.control_socket = context.socket(zmq.ROUTER)
230 self.control_socket.linger = 1000
230 self.control_socket.linger = 1000
231 self.control_port = self._bind_socket(self.control_socket, self.control_port)
231 self.control_port = self._bind_socket(self.control_socket, self.control_port)
232 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
232 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
233
233
234 def init_heartbeat(self):
234 def init_heartbeat(self):
235 """start the heart beating"""
235 """start the heart beating"""
236 # heartbeat doesn't share context, because it mustn't be blocked
236 # heartbeat doesn't share context, because it mustn't be blocked
237 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
237 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
238 hb_ctx = zmq.Context()
238 hb_ctx = zmq.Context()
239 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
239 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
240 self.hb_port = self.heartbeat.port
240 self.hb_port = self.heartbeat.port
241 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
241 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
242 self.heartbeat.start()
242 self.heartbeat.start()
243
243
244 def log_connection_info(self):
244 def log_connection_info(self):
245 """display connection info, and store ports"""
245 """display connection info, and store ports"""
246 basename = os.path.basename(self.connection_file)
246 basename = os.path.basename(self.connection_file)
247 if basename == self.connection_file or \
247 if basename == self.connection_file or \
248 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
248 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
249 # use shortname
249 # use shortname
250 tail = basename
250 tail = basename
251 if self.profile != 'default':
251 if self.profile != 'default':
252 tail += " --profile %s" % self.profile
252 tail += " --profile %s" % self.profile
253 else:
253 else:
254 tail = self.connection_file
254 tail = self.connection_file
255 lines = [
255 lines = [
256 "To connect another client to this kernel, use:",
256 "To connect another client to this kernel, use:",
257 " --existing %s" % tail,
257 " --existing %s" % tail,
258 ]
258 ]
259 # log connection info
259 # log connection info
260 # info-level, so often not shown.
260 # info-level, so often not shown.
261 # frontends should use the %connect_info magic
261 # frontends should use the %connect_info magic
262 # to see the connection info
262 # to see the connection info
263 for line in lines:
263 for line in lines:
264 self.log.info(line)
264 self.log.info(line)
265 # also raw print to the terminal if no parent_handle (`ipython kernel`)
265 # also raw print to the terminal if no parent_handle (`ipython kernel`)
266 if not self.parent_handle:
266 if not self.parent_handle:
267 io.rprint(_ctrl_c_message)
267 io.rprint(_ctrl_c_message)
268 for line in lines:
268 for line in lines:
269 io.rprint(line)
269 io.rprint(line)
270
270
271 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
271 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
272 stdin=self.stdin_port, hb=self.hb_port,
272 stdin=self.stdin_port, hb=self.hb_port,
273 control=self.control_port)
273 control=self.control_port)
274
274
275 def init_blackhole(self):
275 def init_blackhole(self):
276 """redirects stdout/stderr to devnull if necessary"""
276 """redirects stdout/stderr to devnull if necessary"""
277 if self.no_stdout or self.no_stderr:
277 if self.no_stdout or self.no_stderr:
278 blackhole = open(os.devnull, 'w')
278 blackhole = open(os.devnull, 'w')
279 if self.no_stdout:
279 if self.no_stdout:
280 sys.stdout = sys.__stdout__ = blackhole
280 sys.stdout = sys.__stdout__ = blackhole
281 if self.no_stderr:
281 if self.no_stderr:
282 sys.stderr = sys.__stderr__ = blackhole
282 sys.stderr = sys.__stderr__ = blackhole
283
283
284 def init_io(self):
284 def init_io(self):
285 """Redirect input streams and set a display hook."""
285 """Redirect input streams and set a display hook."""
286 if self.outstream_class:
286 if self.outstream_class:
287 outstream_factory = import_item(str(self.outstream_class))
287 outstream_factory = import_item(str(self.outstream_class))
288 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
288 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
289 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
289 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
290 if self.displayhook_class:
290 if self.displayhook_class:
291 displayhook_factory = import_item(str(self.displayhook_class))
291 displayhook_factory = import_item(str(self.displayhook_class))
292 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
292 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
293
293
294 def init_signal(self):
294 def init_signal(self):
295 signal.signal(signal.SIGINT, signal.SIG_IGN)
295 signal.signal(signal.SIGINT, signal.SIG_IGN)
296
296
297 def init_kernel(self):
297 def init_kernel(self):
298 """Create the Kernel object itself"""
298 """Create the Kernel object itself"""
299 shell_stream = ZMQStream(self.shell_socket)
299 shell_stream = ZMQStream(self.shell_socket)
300 control_stream = ZMQStream(self.control_socket)
300 control_stream = ZMQStream(self.control_socket)
301
301
302 kernel_factory = self.kernel_class.instance
302 kernel_factory = self.kernel_class.instance
303
303
304 kernel = kernel_factory(parent=self, session=self.session,
304 kernel = kernel_factory(parent=self, session=self.session,
305 shell_streams=[shell_stream, control_stream],
305 shell_streams=[shell_stream, control_stream],
306 iopub_socket=self.iopub_socket,
306 iopub_socket=self.iopub_socket,
307 stdin_socket=self.stdin_socket,
307 stdin_socket=self.stdin_socket,
308 log=self.log,
308 log=self.log,
309 profile_dir=self.profile_dir,
309 profile_dir=self.profile_dir,
310 user_ns=self.user_ns,
310 user_ns=self.user_ns,
311 )
311 )
312 kernel.record_ports(self.ports)
312 kernel.record_ports(self.ports)
313 self.kernel = kernel
313 self.kernel = kernel
314
314
315 def init_gui_pylab(self):
315 def init_gui_pylab(self):
316 """Enable GUI event loop integration, taking pylab into account."""
316 """Enable GUI event loop integration, taking pylab into account."""
317
317
318 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
318 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
319 # to ensure that any exception is printed straight to stderr.
319 # to ensure that any exception is printed straight to stderr.
320 # Normally _showtraceback associates the reply with an execution,
320 # Normally _showtraceback associates the reply with an execution,
321 # which means frontends will never draw it, as this exception
321 # which means frontends will never draw it, as this exception
322 # is not associated with any execute request.
322 # is not associated with any execute request.
323
323
324 shell = self.shell
324 shell = self.shell
325 _showtraceback = shell._showtraceback
325 _showtraceback = shell._showtraceback
326 try:
326 try:
327 # replace error-sending traceback with stderr
327 # replace error-sending traceback with stderr
328 def print_tb(etype, evalue, stb):
328 def print_tb(etype, evalue, stb):
329 print ("GUI event loop or pylab initialization failed",
329 print ("GUI event loop or pylab initialization failed",
330 file=io.stderr)
330 file=io.stderr)
331 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
331 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
332 shell._showtraceback = print_tb
332 shell._showtraceback = print_tb
333 InteractiveShellApp.init_gui_pylab(self)
333 InteractiveShellApp.init_gui_pylab(self)
334 finally:
334 finally:
335 shell._showtraceback = _showtraceback
335 shell._showtraceback = _showtraceback
336
336
337 def init_shell(self):
337 def init_shell(self):
338 self.shell = getattr(self.kernel, 'shell', None)
338 self.shell = getattr(self.kernel, 'shell', None)
339 if self.shell:
339 if self.shell:
340 self.shell.configurables.append(self)
340 self.shell.configurables.append(self)
341
341
342 @catch_config_error
342 @catch_config_error
343 def initialize(self, argv=None):
343 def initialize(self, argv=None):
344 super(IPKernelApp, self).initialize(argv)
344 super(IPKernelApp, self).initialize(argv)
345 self.init_blackhole()
345 self.init_blackhole()
346 self.init_connection_file()
346 self.init_connection_file()
347 self.init_poller()
347 self.init_poller()
348 self.init_sockets()
348 self.init_sockets()
349 self.init_heartbeat()
349 self.init_heartbeat()
350 # writing/displaying connection info must be *after* init_sockets/heartbeat
350 # writing/displaying connection info must be *after* init_sockets/heartbeat
351 self.log_connection_info()
351 self.log_connection_info()
352 self.write_connection_file()
352 self.write_connection_file()
353 self.init_io()
353 self.init_io()
354 self.init_signal()
354 self.init_signal()
355 self.init_kernel()
355 self.init_kernel()
356 # shell init steps
356 # shell init steps
357 self.init_path()
357 self.init_path()
358 self.init_shell()
358 self.init_shell()
359 if self.shell:
359 if self.shell:
360 self.init_gui_pylab()
360 self.init_gui_pylab()
361 self.init_extensions()
361 self.init_extensions()
362 self.init_code()
362 self.init_code()
363 # flush stdout/stderr, so that anything written to these streams during
363 # flush stdout/stderr, so that anything written to these streams during
364 # initialization do not get associated with the first execution request
364 # initialization do not get associated with the first execution request
365 sys.stdout.flush()
365 sys.stdout.flush()
366 sys.stderr.flush()
366 sys.stderr.flush()
367
367
368 def start(self):
368 def start(self):
369 if self.poller is not None:
369 if self.poller is not None:
370 self.poller.start()
370 self.poller.start()
371 self.kernel.start()
371 self.kernel.start()
372 try:
372 try:
373 ioloop.IOLoop.instance().start()
373 ioloop.IOLoop.instance().start()
374 except KeyboardInterrupt:
374 except KeyboardInterrupt:
375 pass
375 pass
376
376
377 launch_new_instance = IPKernelApp.launch_instance
377 launch_new_instance = IPKernelApp.launch_instance
378
378
379 def main():
379 def main():
380 """Run an IPKernel as an application"""
380 """Run an IPKernel as an application"""
381 app = IPKernelApp.instance()
381 app = IPKernelApp.instance()
382 app.initialize()
382 app.initialize()
383 app.start()
383 app.start()
384
384
385
385
386 if __name__ == '__main__':
386 if __name__ == '__main__':
387 main()
387 main()
@@ -1,701 +1,701 b''
1 """Base class for a kernel that talks to frontends over 0MQ."""
1 """Base class for a kernel that talks to frontends over 0MQ."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import sys
8 import sys
9 import time
9 import time
10 import logging
10 import logging
11 import uuid
11 import uuid
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from signal import (
14 from signal import (
15 signal, default_int_handler, SIGINT
15 signal, default_int_handler, SIGINT
16 )
16 )
17
17
18 import zmq
18 import zmq
19 from zmq.eventloop import ioloop
19 from zmq.eventloop import ioloop
20 from zmq.eventloop.zmqstream import ZMQStream
20 from zmq.eventloop.zmqstream import ZMQStream
21
21
22 from IPython.config.configurable import SingletonConfigurable
22 from IPython.config.configurable import SingletonConfigurable
23 from IPython.core.error import StdinNotImplementedError
23 from IPython.core.error import StdinNotImplementedError
24 from IPython.core import release
24 from IPython.core import release
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.utils.py3compat import unicode_type, string_types
26 from IPython.utils.py3compat import unicode_type, string_types
27 from IPython.utils.jsonutil import json_clean
27 from IPython.utils.jsonutil import json_clean
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 )
30 )
31
31
32 from .session import Session
32 from .session import Session
33
33
34
34
35 class Kernel(SingletonConfigurable):
35 class Kernel(SingletonConfigurable):
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # Kernel interface
38 # Kernel interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 # attribute to override with a GUI
41 # attribute to override with a GUI
42 eventloop = Any(None)
42 eventloop = Any(None)
43 def _eventloop_changed(self, name, old, new):
43 def _eventloop_changed(self, name, old, new):
44 """schedule call to eventloop from IOLoop"""
44 """schedule call to eventloop from IOLoop"""
45 loop = ioloop.IOLoop.instance()
45 loop = ioloop.IOLoop.instance()
46 loop.add_callback(self.enter_eventloop)
46 loop.add_callback(self.enter_eventloop)
47
47
48 session = Instance(Session)
48 session = Instance(Session, allow_none=True)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True)
50 shell_streams = List()
50 shell_streams = List()
51 control_stream = Instance(ZMQStream)
51 control_stream = Instance(ZMQStream, allow_none=True)
52 iopub_socket = Instance(zmq.Socket)
52 iopub_socket = Instance(zmq.Socket, allow_none=True)
53 stdin_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket, allow_none=True)
54 log = Instance(logging.Logger)
54 log = Instance(logging.Logger, allow_none=True)
55
55
56 # identities:
56 # identities:
57 int_id = Integer(-1)
57 int_id = Integer(-1)
58 ident = Unicode()
58 ident = Unicode()
59
59
60 def _ident_default(self):
60 def _ident_default(self):
61 return unicode_type(uuid.uuid4())
61 return unicode_type(uuid.uuid4())
62
62
63 # This should be overridden by wrapper kernels that implement any real
63 # This should be overridden by wrapper kernels that implement any real
64 # language.
64 # language.
65 language_info = {}
65 language_info = {}
66
66
67 # any links that should go in the help menu
67 # any links that should go in the help menu
68 help_links = List()
68 help_links = List()
69
69
70 # Private interface
70 # Private interface
71
71
72 _darwin_app_nap = Bool(True, config=True,
72 _darwin_app_nap = Bool(True, config=True,
73 help="""Whether to use appnope for compatiblity with OS X App Nap.
73 help="""Whether to use appnope for compatiblity with OS X App Nap.
74
74
75 Only affects OS X >= 10.9.
75 Only affects OS X >= 10.9.
76 """
76 """
77 )
77 )
78
78
79 # track associations with current request
79 # track associations with current request
80 _allow_stdin = Bool(False)
80 _allow_stdin = Bool(False)
81 _parent_header = Dict()
81 _parent_header = Dict()
82 _parent_ident = Any(b'')
82 _parent_ident = Any(b'')
83 # Time to sleep after flushing the stdout/err buffers in each execute
83 # Time to sleep after flushing the stdout/err buffers in each execute
84 # cycle. While this introduces a hard limit on the minimal latency of the
84 # cycle. While this introduces a hard limit on the minimal latency of the
85 # execute cycle, it helps prevent output synchronization problems for
85 # execute cycle, it helps prevent output synchronization problems for
86 # clients.
86 # clients.
87 # Units are in seconds. The minimum zmq latency on local host is probably
87 # Units are in seconds. The minimum zmq latency on local host is probably
88 # ~150 microseconds, set this to 500us for now. We may need to increase it
88 # ~150 microseconds, set this to 500us for now. We may need to increase it
89 # a little if it's not enough after more interactive testing.
89 # a little if it's not enough after more interactive testing.
90 _execute_sleep = Float(0.0005, config=True)
90 _execute_sleep = Float(0.0005, config=True)
91
91
92 # Frequency of the kernel's event loop.
92 # Frequency of the kernel's event loop.
93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
94 # adapt to milliseconds.
94 # adapt to milliseconds.
95 _poll_interval = Float(0.05, config=True)
95 _poll_interval = Float(0.05, config=True)
96
96
97 # If the shutdown was requested over the network, we leave here the
97 # If the shutdown was requested over the network, we leave here the
98 # necessary reply message so it can be sent by our registered atexit
98 # necessary reply message so it can be sent by our registered atexit
99 # handler. This ensures that the reply is only sent to clients truly at
99 # handler. This ensures that the reply is only sent to clients truly at
100 # the end of our shutdown process (which happens after the underlying
100 # the end of our shutdown process (which happens after the underlying
101 # IPython shell's own shutdown).
101 # IPython shell's own shutdown).
102 _shutdown_message = None
102 _shutdown_message = None
103
103
104 # This is a dict of port number that the kernel is listening on. It is set
104 # This is a dict of port number that the kernel is listening on. It is set
105 # by record_ports and used by connect_request.
105 # by record_ports and used by connect_request.
106 _recorded_ports = Dict()
106 _recorded_ports = Dict()
107
107
108 # set of aborted msg_ids
108 # set of aborted msg_ids
109 aborted = Set()
109 aborted = Set()
110
110
111 # Track execution count here. For IPython, we override this to use the
111 # Track execution count here. For IPython, we override this to use the
112 # execution count we store in the shell.
112 # execution count we store in the shell.
113 execution_count = 0
113 execution_count = 0
114
114
115
115
116 def __init__(self, **kwargs):
116 def __init__(self, **kwargs):
117 super(Kernel, self).__init__(**kwargs)
117 super(Kernel, self).__init__(**kwargs)
118
118
119 # Build dict of handlers for message types
119 # Build dict of handlers for message types
120 msg_types = [ 'execute_request', 'complete_request',
120 msg_types = [ 'execute_request', 'complete_request',
121 'inspect_request', 'history_request',
121 'inspect_request', 'history_request',
122 'kernel_info_request',
122 'kernel_info_request',
123 'connect_request', 'shutdown_request',
123 'connect_request', 'shutdown_request',
124 'apply_request', 'is_complete_request',
124 'apply_request', 'is_complete_request',
125 ]
125 ]
126 self.shell_handlers = {}
126 self.shell_handlers = {}
127 for msg_type in msg_types:
127 for msg_type in msg_types:
128 self.shell_handlers[msg_type] = getattr(self, msg_type)
128 self.shell_handlers[msg_type] = getattr(self, msg_type)
129
129
130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
131 self.control_handlers = {}
131 self.control_handlers = {}
132 for msg_type in control_msg_types:
132 for msg_type in control_msg_types:
133 self.control_handlers[msg_type] = getattr(self, msg_type)
133 self.control_handlers[msg_type] = getattr(self, msg_type)
134
134
135
135
136 def dispatch_control(self, msg):
136 def dispatch_control(self, msg):
137 """dispatch control requests"""
137 """dispatch control requests"""
138 idents,msg = self.session.feed_identities(msg, copy=False)
138 idents,msg = self.session.feed_identities(msg, copy=False)
139 try:
139 try:
140 msg = self.session.deserialize(msg, content=True, copy=False)
140 msg = self.session.deserialize(msg, content=True, copy=False)
141 except:
141 except:
142 self.log.error("Invalid Control Message", exc_info=True)
142 self.log.error("Invalid Control Message", exc_info=True)
143 return
143 return
144
144
145 self.log.debug("Control received: %s", msg)
145 self.log.debug("Control received: %s", msg)
146
146
147 # Set the parent message for side effects.
147 # Set the parent message for side effects.
148 self.set_parent(idents, msg)
148 self.set_parent(idents, msg)
149 self._publish_status(u'busy')
149 self._publish_status(u'busy')
150
150
151 header = msg['header']
151 header = msg['header']
152 msg_type = header['msg_type']
152 msg_type = header['msg_type']
153
153
154 handler = self.control_handlers.get(msg_type, None)
154 handler = self.control_handlers.get(msg_type, None)
155 if handler is None:
155 if handler is None:
156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
157 else:
157 else:
158 try:
158 try:
159 handler(self.control_stream, idents, msg)
159 handler(self.control_stream, idents, msg)
160 except Exception:
160 except Exception:
161 self.log.error("Exception in control handler:", exc_info=True)
161 self.log.error("Exception in control handler:", exc_info=True)
162
162
163 sys.stdout.flush()
163 sys.stdout.flush()
164 sys.stderr.flush()
164 sys.stderr.flush()
165 self._publish_status(u'idle')
165 self._publish_status(u'idle')
166
166
167 def dispatch_shell(self, stream, msg):
167 def dispatch_shell(self, stream, msg):
168 """dispatch shell requests"""
168 """dispatch shell requests"""
169 # flush control requests first
169 # flush control requests first
170 if self.control_stream:
170 if self.control_stream:
171 self.control_stream.flush()
171 self.control_stream.flush()
172
172
173 idents,msg = self.session.feed_identities(msg, copy=False)
173 idents,msg = self.session.feed_identities(msg, copy=False)
174 try:
174 try:
175 msg = self.session.deserialize(msg, content=True, copy=False)
175 msg = self.session.deserialize(msg, content=True, copy=False)
176 except:
176 except:
177 self.log.error("Invalid Message", exc_info=True)
177 self.log.error("Invalid Message", exc_info=True)
178 return
178 return
179
179
180 # Set the parent message for side effects.
180 # Set the parent message for side effects.
181 self.set_parent(idents, msg)
181 self.set_parent(idents, msg)
182 self._publish_status(u'busy')
182 self._publish_status(u'busy')
183
183
184 header = msg['header']
184 header = msg['header']
185 msg_id = header['msg_id']
185 msg_id = header['msg_id']
186 msg_type = msg['header']['msg_type']
186 msg_type = msg['header']['msg_type']
187
187
188 # Print some info about this message and leave a '--->' marker, so it's
188 # Print some info about this message and leave a '--->' marker, so it's
189 # easier to trace visually the message chain when debugging. Each
189 # easier to trace visually the message chain when debugging. Each
190 # handler prints its message at the end.
190 # handler prints its message at the end.
191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
193
193
194 if msg_id in self.aborted:
194 if msg_id in self.aborted:
195 self.aborted.remove(msg_id)
195 self.aborted.remove(msg_id)
196 # is it safe to assume a msg_id will not be resubmitted?
196 # is it safe to assume a msg_id will not be resubmitted?
197 reply_type = msg_type.split('_')[0] + '_reply'
197 reply_type = msg_type.split('_')[0] + '_reply'
198 status = {'status' : 'aborted'}
198 status = {'status' : 'aborted'}
199 md = {'engine' : self.ident}
199 md = {'engine' : self.ident}
200 md.update(status)
200 md.update(status)
201 self.session.send(stream, reply_type, metadata=md,
201 self.session.send(stream, reply_type, metadata=md,
202 content=status, parent=msg, ident=idents)
202 content=status, parent=msg, ident=idents)
203 return
203 return
204
204
205 handler = self.shell_handlers.get(msg_type, None)
205 handler = self.shell_handlers.get(msg_type, None)
206 if handler is None:
206 if handler is None:
207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
208 else:
208 else:
209 # ensure default_int_handler during handler call
209 # ensure default_int_handler during handler call
210 sig = signal(SIGINT, default_int_handler)
210 sig = signal(SIGINT, default_int_handler)
211 self.log.debug("%s: %s", msg_type, msg)
211 self.log.debug("%s: %s", msg_type, msg)
212 try:
212 try:
213 handler(stream, idents, msg)
213 handler(stream, idents, msg)
214 except Exception:
214 except Exception:
215 self.log.error("Exception in message handler:", exc_info=True)
215 self.log.error("Exception in message handler:", exc_info=True)
216 finally:
216 finally:
217 signal(SIGINT, sig)
217 signal(SIGINT, sig)
218
218
219 sys.stdout.flush()
219 sys.stdout.flush()
220 sys.stderr.flush()
220 sys.stderr.flush()
221 self._publish_status(u'idle')
221 self._publish_status(u'idle')
222
222
223 def enter_eventloop(self):
223 def enter_eventloop(self):
224 """enter eventloop"""
224 """enter eventloop"""
225 self.log.info("entering eventloop %s", self.eventloop)
225 self.log.info("entering eventloop %s", self.eventloop)
226 for stream in self.shell_streams:
226 for stream in self.shell_streams:
227 # flush any pending replies,
227 # flush any pending replies,
228 # which may be skipped by entering the eventloop
228 # which may be skipped by entering the eventloop
229 stream.flush(zmq.POLLOUT)
229 stream.flush(zmq.POLLOUT)
230 # restore default_int_handler
230 # restore default_int_handler
231 signal(SIGINT, default_int_handler)
231 signal(SIGINT, default_int_handler)
232 while self.eventloop is not None:
232 while self.eventloop is not None:
233 try:
233 try:
234 self.eventloop(self)
234 self.eventloop(self)
235 except KeyboardInterrupt:
235 except KeyboardInterrupt:
236 # Ctrl-C shouldn't crash the kernel
236 # Ctrl-C shouldn't crash the kernel
237 self.log.error("KeyboardInterrupt caught in kernel")
237 self.log.error("KeyboardInterrupt caught in kernel")
238 continue
238 continue
239 else:
239 else:
240 # eventloop exited cleanly, this means we should stop (right?)
240 # eventloop exited cleanly, this means we should stop (right?)
241 self.eventloop = None
241 self.eventloop = None
242 break
242 break
243 self.log.info("exiting eventloop")
243 self.log.info("exiting eventloop")
244
244
245 def start(self):
245 def start(self):
246 """register dispatchers for streams"""
246 """register dispatchers for streams"""
247 if self.control_stream:
247 if self.control_stream:
248 self.control_stream.on_recv(self.dispatch_control, copy=False)
248 self.control_stream.on_recv(self.dispatch_control, copy=False)
249
249
250 def make_dispatcher(stream):
250 def make_dispatcher(stream):
251 def dispatcher(msg):
251 def dispatcher(msg):
252 return self.dispatch_shell(stream, msg)
252 return self.dispatch_shell(stream, msg)
253 return dispatcher
253 return dispatcher
254
254
255 for s in self.shell_streams:
255 for s in self.shell_streams:
256 s.on_recv(make_dispatcher(s), copy=False)
256 s.on_recv(make_dispatcher(s), copy=False)
257
257
258 # publish idle status
258 # publish idle status
259 self._publish_status('starting')
259 self._publish_status('starting')
260
260
261 def do_one_iteration(self):
261 def do_one_iteration(self):
262 """step eventloop just once"""
262 """step eventloop just once"""
263 if self.control_stream:
263 if self.control_stream:
264 self.control_stream.flush()
264 self.control_stream.flush()
265 for stream in self.shell_streams:
265 for stream in self.shell_streams:
266 # handle at most one request per iteration
266 # handle at most one request per iteration
267 stream.flush(zmq.POLLIN, 1)
267 stream.flush(zmq.POLLIN, 1)
268 stream.flush(zmq.POLLOUT)
268 stream.flush(zmq.POLLOUT)
269
269
270
270
271 def record_ports(self, ports):
271 def record_ports(self, ports):
272 """Record the ports that this kernel is using.
272 """Record the ports that this kernel is using.
273
273
274 The creator of the Kernel instance must call this methods if they
274 The creator of the Kernel instance must call this methods if they
275 want the :meth:`connect_request` method to return the port numbers.
275 want the :meth:`connect_request` method to return the port numbers.
276 """
276 """
277 self._recorded_ports = ports
277 self._recorded_ports = ports
278
278
279 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
280 # Kernel request handlers
280 # Kernel request handlers
281 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
282
282
283 def _make_metadata(self, other=None):
283 def _make_metadata(self, other=None):
284 """init metadata dict, for execute/apply_reply"""
284 """init metadata dict, for execute/apply_reply"""
285 new_md = {
285 new_md = {
286 'dependencies_met' : True,
286 'dependencies_met' : True,
287 'engine' : self.ident,
287 'engine' : self.ident,
288 'started': datetime.now(),
288 'started': datetime.now(),
289 }
289 }
290 if other:
290 if other:
291 new_md.update(other)
291 new_md.update(other)
292 return new_md
292 return new_md
293
293
294 def _publish_execute_input(self, code, parent, execution_count):
294 def _publish_execute_input(self, code, parent, execution_count):
295 """Publish the code request on the iopub stream."""
295 """Publish the code request on the iopub stream."""
296
296
297 self.session.send(self.iopub_socket, u'execute_input',
297 self.session.send(self.iopub_socket, u'execute_input',
298 {u'code':code, u'execution_count': execution_count},
298 {u'code':code, u'execution_count': execution_count},
299 parent=parent, ident=self._topic('execute_input')
299 parent=parent, ident=self._topic('execute_input')
300 )
300 )
301
301
302 def _publish_status(self, status, parent=None):
302 def _publish_status(self, status, parent=None):
303 """send status (busy/idle) on IOPub"""
303 """send status (busy/idle) on IOPub"""
304 self.session.send(self.iopub_socket,
304 self.session.send(self.iopub_socket,
305 u'status',
305 u'status',
306 {u'execution_state': status},
306 {u'execution_state': status},
307 parent=parent or self._parent_header,
307 parent=parent or self._parent_header,
308 ident=self._topic('status'),
308 ident=self._topic('status'),
309 )
309 )
310
310
311 def set_parent(self, ident, parent):
311 def set_parent(self, ident, parent):
312 """Set the current parent_header
312 """Set the current parent_header
313
313
314 Side effects (IOPub messages) and replies are associated with
314 Side effects (IOPub messages) and replies are associated with
315 the request that caused them via the parent_header.
315 the request that caused them via the parent_header.
316
316
317 The parent identity is used to route input_request messages
317 The parent identity is used to route input_request messages
318 on the stdin channel.
318 on the stdin channel.
319 """
319 """
320 self._parent_ident = ident
320 self._parent_ident = ident
321 self._parent_header = parent
321 self._parent_header = parent
322
322
323 def send_response(self, stream, msg_or_type, content=None, ident=None,
323 def send_response(self, stream, msg_or_type, content=None, ident=None,
324 buffers=None, track=False, header=None, metadata=None):
324 buffers=None, track=False, header=None, metadata=None):
325 """Send a response to the message we're currently processing.
325 """Send a response to the message we're currently processing.
326
326
327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
328 except ``parent``.
328 except ``parent``.
329
329
330 This relies on :meth:`set_parent` having been called for the current
330 This relies on :meth:`set_parent` having been called for the current
331 message.
331 message.
332 """
332 """
333 return self.session.send(stream, msg_or_type, content, self._parent_header,
333 return self.session.send(stream, msg_or_type, content, self._parent_header,
334 ident, buffers, track, header, metadata)
334 ident, buffers, track, header, metadata)
335
335
336 def execute_request(self, stream, ident, parent):
336 def execute_request(self, stream, ident, parent):
337 """handle an execute_request"""
337 """handle an execute_request"""
338
338
339 try:
339 try:
340 content = parent[u'content']
340 content = parent[u'content']
341 code = py3compat.cast_unicode_py2(content[u'code'])
341 code = py3compat.cast_unicode_py2(content[u'code'])
342 silent = content[u'silent']
342 silent = content[u'silent']
343 store_history = content.get(u'store_history', not silent)
343 store_history = content.get(u'store_history', not silent)
344 user_expressions = content.get('user_expressions', {})
344 user_expressions = content.get('user_expressions', {})
345 allow_stdin = content.get('allow_stdin', False)
345 allow_stdin = content.get('allow_stdin', False)
346 except:
346 except:
347 self.log.error("Got bad msg: ")
347 self.log.error("Got bad msg: ")
348 self.log.error("%s", parent)
348 self.log.error("%s", parent)
349 return
349 return
350
350
351 stop_on_error = content.get('stop_on_error', True)
351 stop_on_error = content.get('stop_on_error', True)
352
352
353 md = self._make_metadata(parent['metadata'])
353 md = self._make_metadata(parent['metadata'])
354
354
355 # Re-broadcast our input for the benefit of listening clients, and
355 # Re-broadcast our input for the benefit of listening clients, and
356 # start computing output
356 # start computing output
357 if not silent:
357 if not silent:
358 self.execution_count += 1
358 self.execution_count += 1
359 self._publish_execute_input(code, parent, self.execution_count)
359 self._publish_execute_input(code, parent, self.execution_count)
360
360
361 reply_content = self.do_execute(code, silent, store_history,
361 reply_content = self.do_execute(code, silent, store_history,
362 user_expressions, allow_stdin)
362 user_expressions, allow_stdin)
363
363
364 # Flush output before sending the reply.
364 # Flush output before sending the reply.
365 sys.stdout.flush()
365 sys.stdout.flush()
366 sys.stderr.flush()
366 sys.stderr.flush()
367 # FIXME: on rare occasions, the flush doesn't seem to make it to the
367 # FIXME: on rare occasions, the flush doesn't seem to make it to the
368 # clients... This seems to mitigate the problem, but we definitely need
368 # clients... This seems to mitigate the problem, but we definitely need
369 # to better understand what's going on.
369 # to better understand what's going on.
370 if self._execute_sleep:
370 if self._execute_sleep:
371 time.sleep(self._execute_sleep)
371 time.sleep(self._execute_sleep)
372
372
373 # Send the reply.
373 # Send the reply.
374 reply_content = json_clean(reply_content)
374 reply_content = json_clean(reply_content)
375
375
376 md['status'] = reply_content['status']
376 md['status'] = reply_content['status']
377 if reply_content['status'] == 'error' and \
377 if reply_content['status'] == 'error' and \
378 reply_content['ename'] == 'UnmetDependency':
378 reply_content['ename'] == 'UnmetDependency':
379 md['dependencies_met'] = False
379 md['dependencies_met'] = False
380
380
381 reply_msg = self.session.send(stream, u'execute_reply',
381 reply_msg = self.session.send(stream, u'execute_reply',
382 reply_content, parent, metadata=md,
382 reply_content, parent, metadata=md,
383 ident=ident)
383 ident=ident)
384
384
385 self.log.debug("%s", reply_msg)
385 self.log.debug("%s", reply_msg)
386
386
387 if not silent and reply_msg['content']['status'] == u'error' and stop_on_error:
387 if not silent and reply_msg['content']['status'] == u'error' and stop_on_error:
388 self._abort_queues()
388 self._abort_queues()
389
389
390 def do_execute(self, code, silent, store_history=True,
390 def do_execute(self, code, silent, store_history=True,
391 user_expressions=None, allow_stdin=False):
391 user_expressions=None, allow_stdin=False):
392 """Execute user code. Must be overridden by subclasses.
392 """Execute user code. Must be overridden by subclasses.
393 """
393 """
394 raise NotImplementedError
394 raise NotImplementedError
395
395
396 def complete_request(self, stream, ident, parent):
396 def complete_request(self, stream, ident, parent):
397 content = parent['content']
397 content = parent['content']
398 code = content['code']
398 code = content['code']
399 cursor_pos = content['cursor_pos']
399 cursor_pos = content['cursor_pos']
400
400
401 matches = self.do_complete(code, cursor_pos)
401 matches = self.do_complete(code, cursor_pos)
402 matches = json_clean(matches)
402 matches = json_clean(matches)
403 completion_msg = self.session.send(stream, 'complete_reply',
403 completion_msg = self.session.send(stream, 'complete_reply',
404 matches, parent, ident)
404 matches, parent, ident)
405 self.log.debug("%s", completion_msg)
405 self.log.debug("%s", completion_msg)
406
406
407 def do_complete(self, code, cursor_pos):
407 def do_complete(self, code, cursor_pos):
408 """Override in subclasses to find completions.
408 """Override in subclasses to find completions.
409 """
409 """
410 return {'matches' : [],
410 return {'matches' : [],
411 'cursor_end' : cursor_pos,
411 'cursor_end' : cursor_pos,
412 'cursor_start' : cursor_pos,
412 'cursor_start' : cursor_pos,
413 'metadata' : {},
413 'metadata' : {},
414 'status' : 'ok'}
414 'status' : 'ok'}
415
415
416 def inspect_request(self, stream, ident, parent):
416 def inspect_request(self, stream, ident, parent):
417 content = parent['content']
417 content = parent['content']
418
418
419 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
419 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
420 content.get('detail_level', 0))
420 content.get('detail_level', 0))
421 # Before we send this object over, we scrub it for JSON usage
421 # Before we send this object over, we scrub it for JSON usage
422 reply_content = json_clean(reply_content)
422 reply_content = json_clean(reply_content)
423 msg = self.session.send(stream, 'inspect_reply',
423 msg = self.session.send(stream, 'inspect_reply',
424 reply_content, parent, ident)
424 reply_content, parent, ident)
425 self.log.debug("%s", msg)
425 self.log.debug("%s", msg)
426
426
427 def do_inspect(self, code, cursor_pos, detail_level=0):
427 def do_inspect(self, code, cursor_pos, detail_level=0):
428 """Override in subclasses to allow introspection.
428 """Override in subclasses to allow introspection.
429 """
429 """
430 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
430 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
431
431
432 def history_request(self, stream, ident, parent):
432 def history_request(self, stream, ident, parent):
433 content = parent['content']
433 content = parent['content']
434
434
435 reply_content = self.do_history(**content)
435 reply_content = self.do_history(**content)
436
436
437 reply_content = json_clean(reply_content)
437 reply_content = json_clean(reply_content)
438 msg = self.session.send(stream, 'history_reply',
438 msg = self.session.send(stream, 'history_reply',
439 reply_content, parent, ident)
439 reply_content, parent, ident)
440 self.log.debug("%s", msg)
440 self.log.debug("%s", msg)
441
441
442 def do_history(self, hist_access_type, output, raw, session=None, start=None,
442 def do_history(self, hist_access_type, output, raw, session=None, start=None,
443 stop=None, n=None, pattern=None, unique=False):
443 stop=None, n=None, pattern=None, unique=False):
444 """Override in subclasses to access history.
444 """Override in subclasses to access history.
445 """
445 """
446 return {'history': []}
446 return {'history': []}
447
447
448 def connect_request(self, stream, ident, parent):
448 def connect_request(self, stream, ident, parent):
449 if self._recorded_ports is not None:
449 if self._recorded_ports is not None:
450 content = self._recorded_ports.copy()
450 content = self._recorded_ports.copy()
451 else:
451 else:
452 content = {}
452 content = {}
453 msg = self.session.send(stream, 'connect_reply',
453 msg = self.session.send(stream, 'connect_reply',
454 content, parent, ident)
454 content, parent, ident)
455 self.log.debug("%s", msg)
455 self.log.debug("%s", msg)
456
456
457 @property
457 @property
458 def kernel_info(self):
458 def kernel_info(self):
459 return {
459 return {
460 'protocol_version': release.kernel_protocol_version,
460 'protocol_version': release.kernel_protocol_version,
461 'implementation': self.implementation,
461 'implementation': self.implementation,
462 'implementation_version': self.implementation_version,
462 'implementation_version': self.implementation_version,
463 'language_info': self.language_info,
463 'language_info': self.language_info,
464 'banner': self.banner,
464 'banner': self.banner,
465 'help_links': self.help_links,
465 'help_links': self.help_links,
466 }
466 }
467
467
468 def kernel_info_request(self, stream, ident, parent):
468 def kernel_info_request(self, stream, ident, parent):
469 msg = self.session.send(stream, 'kernel_info_reply',
469 msg = self.session.send(stream, 'kernel_info_reply',
470 self.kernel_info, parent, ident)
470 self.kernel_info, parent, ident)
471 self.log.debug("%s", msg)
471 self.log.debug("%s", msg)
472
472
473 def shutdown_request(self, stream, ident, parent):
473 def shutdown_request(self, stream, ident, parent):
474 content = self.do_shutdown(parent['content']['restart'])
474 content = self.do_shutdown(parent['content']['restart'])
475 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
475 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
476 # same content, but different msg_id for broadcasting on IOPub
476 # same content, but different msg_id for broadcasting on IOPub
477 self._shutdown_message = self.session.msg(u'shutdown_reply',
477 self._shutdown_message = self.session.msg(u'shutdown_reply',
478 content, parent
478 content, parent
479 )
479 )
480
480
481 self._at_shutdown()
481 self._at_shutdown()
482 # call sys.exit after a short delay
482 # call sys.exit after a short delay
483 loop = ioloop.IOLoop.instance()
483 loop = ioloop.IOLoop.instance()
484 loop.add_timeout(time.time()+0.1, loop.stop)
484 loop.add_timeout(time.time()+0.1, loop.stop)
485
485
486 def do_shutdown(self, restart):
486 def do_shutdown(self, restart):
487 """Override in subclasses to do things when the frontend shuts down the
487 """Override in subclasses to do things when the frontend shuts down the
488 kernel.
488 kernel.
489 """
489 """
490 return {'status': 'ok', 'restart': restart}
490 return {'status': 'ok', 'restart': restart}
491
491
492 def is_complete_request(self, stream, ident, parent):
492 def is_complete_request(self, stream, ident, parent):
493 content = parent['content']
493 content = parent['content']
494 code = content['code']
494 code = content['code']
495
495
496 reply_content = self.do_is_complete(code)
496 reply_content = self.do_is_complete(code)
497 reply_content = json_clean(reply_content)
497 reply_content = json_clean(reply_content)
498 reply_msg = self.session.send(stream, 'is_complete_reply',
498 reply_msg = self.session.send(stream, 'is_complete_reply',
499 reply_content, parent, ident)
499 reply_content, parent, ident)
500 self.log.debug("%s", reply_msg)
500 self.log.debug("%s", reply_msg)
501
501
502 def do_is_complete(self, code):
502 def do_is_complete(self, code):
503 """Override in subclasses to find completions.
503 """Override in subclasses to find completions.
504 """
504 """
505 return {'status' : 'unknown',
505 return {'status' : 'unknown',
506 }
506 }
507
507
508 #---------------------------------------------------------------------------
508 #---------------------------------------------------------------------------
509 # Engine methods
509 # Engine methods
510 #---------------------------------------------------------------------------
510 #---------------------------------------------------------------------------
511
511
512 def apply_request(self, stream, ident, parent):
512 def apply_request(self, stream, ident, parent):
513 try:
513 try:
514 content = parent[u'content']
514 content = parent[u'content']
515 bufs = parent[u'buffers']
515 bufs = parent[u'buffers']
516 msg_id = parent['header']['msg_id']
516 msg_id = parent['header']['msg_id']
517 except:
517 except:
518 self.log.error("Got bad msg: %s", parent, exc_info=True)
518 self.log.error("Got bad msg: %s", parent, exc_info=True)
519 return
519 return
520
520
521 md = self._make_metadata(parent['metadata'])
521 md = self._make_metadata(parent['metadata'])
522
522
523 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
523 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
524
524
525 # put 'ok'/'error' status in header, for scheduler introspection:
525 # put 'ok'/'error' status in header, for scheduler introspection:
526 md['status'] = reply_content['status']
526 md['status'] = reply_content['status']
527
527
528 # flush i/o
528 # flush i/o
529 sys.stdout.flush()
529 sys.stdout.flush()
530 sys.stderr.flush()
530 sys.stderr.flush()
531
531
532 self.session.send(stream, u'apply_reply', reply_content,
532 self.session.send(stream, u'apply_reply', reply_content,
533 parent=parent, ident=ident,buffers=result_buf, metadata=md)
533 parent=parent, ident=ident,buffers=result_buf, metadata=md)
534
534
535 def do_apply(self, content, bufs, msg_id, reply_metadata):
535 def do_apply(self, content, bufs, msg_id, reply_metadata):
536 """Override in subclasses to support the IPython parallel framework.
536 """Override in subclasses to support the IPython parallel framework.
537 """
537 """
538 raise NotImplementedError
538 raise NotImplementedError
539
539
540 #---------------------------------------------------------------------------
540 #---------------------------------------------------------------------------
541 # Control messages
541 # Control messages
542 #---------------------------------------------------------------------------
542 #---------------------------------------------------------------------------
543
543
544 def abort_request(self, stream, ident, parent):
544 def abort_request(self, stream, ident, parent):
545 """abort a specific msg by id"""
545 """abort a specific msg by id"""
546 msg_ids = parent['content'].get('msg_ids', None)
546 msg_ids = parent['content'].get('msg_ids', None)
547 if isinstance(msg_ids, string_types):
547 if isinstance(msg_ids, string_types):
548 msg_ids = [msg_ids]
548 msg_ids = [msg_ids]
549 if not msg_ids:
549 if not msg_ids:
550 self._abort_queues()
550 self._abort_queues()
551 for mid in msg_ids:
551 for mid in msg_ids:
552 self.aborted.add(str(mid))
552 self.aborted.add(str(mid))
553
553
554 content = dict(status='ok')
554 content = dict(status='ok')
555 reply_msg = self.session.send(stream, 'abort_reply', content=content,
555 reply_msg = self.session.send(stream, 'abort_reply', content=content,
556 parent=parent, ident=ident)
556 parent=parent, ident=ident)
557 self.log.debug("%s", reply_msg)
557 self.log.debug("%s", reply_msg)
558
558
559 def clear_request(self, stream, idents, parent):
559 def clear_request(self, stream, idents, parent):
560 """Clear our namespace."""
560 """Clear our namespace."""
561 content = self.do_clear()
561 content = self.do_clear()
562 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
562 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
563 content = content)
563 content = content)
564
564
565 def do_clear(self):
565 def do_clear(self):
566 """Override in subclasses to clear the namespace
566 """Override in subclasses to clear the namespace
567
567
568 This is only required for IPython.parallel.
568 This is only required for IPython.parallel.
569 """
569 """
570 raise NotImplementedError
570 raise NotImplementedError
571
571
572 #---------------------------------------------------------------------------
572 #---------------------------------------------------------------------------
573 # Protected interface
573 # Protected interface
574 #---------------------------------------------------------------------------
574 #---------------------------------------------------------------------------
575
575
576 def _topic(self, topic):
576 def _topic(self, topic):
577 """prefixed topic for IOPub messages"""
577 """prefixed topic for IOPub messages"""
578 if self.int_id >= 0:
578 if self.int_id >= 0:
579 base = "engine.%i" % self.int_id
579 base = "engine.%i" % self.int_id
580 else:
580 else:
581 base = "kernel.%s" % self.ident
581 base = "kernel.%s" % self.ident
582
582
583 return py3compat.cast_bytes("%s.%s" % (base, topic))
583 return py3compat.cast_bytes("%s.%s" % (base, topic))
584
584
585 def _abort_queues(self):
585 def _abort_queues(self):
586 for stream in self.shell_streams:
586 for stream in self.shell_streams:
587 if stream:
587 if stream:
588 self._abort_queue(stream)
588 self._abort_queue(stream)
589
589
590 def _abort_queue(self, stream):
590 def _abort_queue(self, stream):
591 poller = zmq.Poller()
591 poller = zmq.Poller()
592 poller.register(stream.socket, zmq.POLLIN)
592 poller.register(stream.socket, zmq.POLLIN)
593 while True:
593 while True:
594 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
594 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
595 if msg is None:
595 if msg is None:
596 return
596 return
597
597
598 self.log.info("Aborting:")
598 self.log.info("Aborting:")
599 self.log.info("%s", msg)
599 self.log.info("%s", msg)
600 msg_type = msg['header']['msg_type']
600 msg_type = msg['header']['msg_type']
601 reply_type = msg_type.split('_')[0] + '_reply'
601 reply_type = msg_type.split('_')[0] + '_reply'
602
602
603 status = {'status' : 'aborted'}
603 status = {'status' : 'aborted'}
604 md = {'engine' : self.ident}
604 md = {'engine' : self.ident}
605 md.update(status)
605 md.update(status)
606 reply_msg = self.session.send(stream, reply_type, metadata=md,
606 reply_msg = self.session.send(stream, reply_type, metadata=md,
607 content=status, parent=msg, ident=idents)
607 content=status, parent=msg, ident=idents)
608 self.log.debug("%s", reply_msg)
608 self.log.debug("%s", reply_msg)
609 # We need to wait a bit for requests to come in. This can probably
609 # We need to wait a bit for requests to come in. This can probably
610 # be set shorter for true asynchronous clients.
610 # be set shorter for true asynchronous clients.
611 poller.poll(50)
611 poller.poll(50)
612
612
613
613
614 def _no_raw_input(self):
614 def _no_raw_input(self):
615 """Raise StdinNotImplentedError if active frontend doesn't support
615 """Raise StdinNotImplentedError if active frontend doesn't support
616 stdin."""
616 stdin."""
617 raise StdinNotImplementedError("raw_input was called, but this "
617 raise StdinNotImplementedError("raw_input was called, but this "
618 "frontend does not support stdin.")
618 "frontend does not support stdin.")
619
619
620 def getpass(self, prompt=''):
620 def getpass(self, prompt=''):
621 """Forward getpass to frontends
621 """Forward getpass to frontends
622
622
623 Raises
623 Raises
624 ------
624 ------
625 StdinNotImplentedError if active frontend doesn't support stdin.
625 StdinNotImplentedError if active frontend doesn't support stdin.
626 """
626 """
627 if not self._allow_stdin:
627 if not self._allow_stdin:
628 raise StdinNotImplementedError(
628 raise StdinNotImplementedError(
629 "getpass was called, but this frontend does not support input requests."
629 "getpass was called, but this frontend does not support input requests."
630 )
630 )
631 return self._input_request(prompt,
631 return self._input_request(prompt,
632 self._parent_ident,
632 self._parent_ident,
633 self._parent_header,
633 self._parent_header,
634 password=True,
634 password=True,
635 )
635 )
636
636
637 def raw_input(self, prompt=''):
637 def raw_input(self, prompt=''):
638 """Forward raw_input to frontends
638 """Forward raw_input to frontends
639
639
640 Raises
640 Raises
641 ------
641 ------
642 StdinNotImplentedError if active frontend doesn't support stdin.
642 StdinNotImplentedError if active frontend doesn't support stdin.
643 """
643 """
644 if not self._allow_stdin:
644 if not self._allow_stdin:
645 raise StdinNotImplementedError(
645 raise StdinNotImplementedError(
646 "raw_input was called, but this frontend does not support input requests."
646 "raw_input was called, but this frontend does not support input requests."
647 )
647 )
648 return self._input_request(prompt,
648 return self._input_request(prompt,
649 self._parent_ident,
649 self._parent_ident,
650 self._parent_header,
650 self._parent_header,
651 password=False,
651 password=False,
652 )
652 )
653
653
654 def _input_request(self, prompt, ident, parent, password=False):
654 def _input_request(self, prompt, ident, parent, password=False):
655 # Flush output before making the request.
655 # Flush output before making the request.
656 sys.stderr.flush()
656 sys.stderr.flush()
657 sys.stdout.flush()
657 sys.stdout.flush()
658 # flush the stdin socket, to purge stale replies
658 # flush the stdin socket, to purge stale replies
659 while True:
659 while True:
660 try:
660 try:
661 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
661 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
662 except zmq.ZMQError as e:
662 except zmq.ZMQError as e:
663 if e.errno == zmq.EAGAIN:
663 if e.errno == zmq.EAGAIN:
664 break
664 break
665 else:
665 else:
666 raise
666 raise
667
667
668 # Send the input request.
668 # Send the input request.
669 content = json_clean(dict(prompt=prompt, password=password))
669 content = json_clean(dict(prompt=prompt, password=password))
670 self.session.send(self.stdin_socket, u'input_request', content, parent,
670 self.session.send(self.stdin_socket, u'input_request', content, parent,
671 ident=ident)
671 ident=ident)
672
672
673 # Await a response.
673 # Await a response.
674 while True:
674 while True:
675 try:
675 try:
676 ident, reply = self.session.recv(self.stdin_socket, 0)
676 ident, reply = self.session.recv(self.stdin_socket, 0)
677 except Exception:
677 except Exception:
678 self.log.warn("Invalid Message:", exc_info=True)
678 self.log.warn("Invalid Message:", exc_info=True)
679 except KeyboardInterrupt:
679 except KeyboardInterrupt:
680 # re-raise KeyboardInterrupt, to truncate traceback
680 # re-raise KeyboardInterrupt, to truncate traceback
681 raise KeyboardInterrupt
681 raise KeyboardInterrupt
682 else:
682 else:
683 break
683 break
684 try:
684 try:
685 value = py3compat.unicode_to_str(reply['content']['value'])
685 value = py3compat.unicode_to_str(reply['content']['value'])
686 except:
686 except:
687 self.log.error("Bad input_reply: %s", parent)
687 self.log.error("Bad input_reply: %s", parent)
688 value = ''
688 value = ''
689 if value == '\x04':
689 if value == '\x04':
690 # EOF
690 # EOF
691 raise EOFError
691 raise EOFError
692 return value
692 return value
693
693
694 def _at_shutdown(self):
694 def _at_shutdown(self):
695 """Actions taken at shutdown by the kernel, called by python's atexit.
695 """Actions taken at shutdown by the kernel, called by python's atexit.
696 """
696 """
697 # io.rprint("Kernel at_shutdown") # dbg
697 # io.rprint("Kernel at_shutdown") # dbg
698 if self._shutdown_message is not None:
698 if self._shutdown_message is not None:
699 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
699 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
700 self.log.debug("%s", self._shutdown_message)
700 self.log.debug("%s", self._shutdown_message)
701 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
701 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now