##// 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 1 # encoding: utf-8
2 2 """A base class for a configurable application."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import json
10 10 import logging
11 11 import os
12 12 import re
13 13 import sys
14 14 from copy import deepcopy
15 15 from collections import defaultdict
16 16
17 17 from decorator import decorator
18 18
19 19 from IPython.config.configurable import SingletonConfigurable
20 20 from IPython.config.loader import (
21 21 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
22 22 )
23 23
24 24 from IPython.utils.traitlets import (
25 25 Unicode, List, Enum, Dict, Instance, TraitError
26 26 )
27 27 from IPython.utils.importstring import import_item
28 28 from IPython.utils.text import indent, wrap_paragraphs, dedent
29 29 from IPython.utils import py3compat
30 30 from IPython.utils.py3compat import string_types, iteritems
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Descriptions for the various sections
34 34 #-----------------------------------------------------------------------------
35 35
36 36 # merge flags&aliases into options
37 37 option_description = """
38 38 Arguments that take values are actually convenience aliases to full
39 39 Configurables, whose aliases are listed on the help line. For more information
40 40 on full configurables, see '--help-all'.
41 41 """.strip() # trim newlines of front and back
42 42
43 43 keyvalue_description = """
44 44 Parameters are set from command-line arguments of the form:
45 45 `--Class.trait=value`.
46 46 This line is evaluated in Python, so simple expressions are allowed, e.g.::
47 47 `--C.a='range(3)'` For setting C.a=[0,1,2].
48 48 """.strip() # trim newlines of front and back
49 49
50 50 # sys.argv can be missing, for example when python is embedded. See the docs
51 51 # for details: http://docs.python.org/2/c-api/intro.html#embedding-python
52 52 if not hasattr(sys, "argv"):
53 53 sys.argv = [""]
54 54
55 55 subcommand_description = """
56 56 Subcommands are launched as `{app} cmd [args]`. For information on using
57 57 subcommand 'cmd', do: `{app} cmd -h`.
58 58 """
59 59 # get running program name
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Application class
63 63 #-----------------------------------------------------------------------------
64 64
65 65 @decorator
66 66 def catch_config_error(method, app, *args, **kwargs):
67 67 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
68 68
69 69 On a TraitError (generally caused by bad config), this will print the trait's
70 70 message, and exit the app.
71 71
72 72 For use on init methods, to prevent invoking excepthook on invalid input.
73 73 """
74 74 try:
75 75 return method(app, *args, **kwargs)
76 76 except (TraitError, ArgumentError) as e:
77 77 app.print_help()
78 78 app.log.fatal("Bad config encountered during initialization:")
79 79 app.log.fatal(str(e))
80 80 app.log.debug("Config at the time: %s", app.config)
81 81 app.exit(1)
82 82
83 83
84 84 class ApplicationError(Exception):
85 85 pass
86 86
87 87 class LevelFormatter(logging.Formatter):
88 88 """Formatter with additional `highlevel` record
89 89
90 90 This field is empty if log level is less than highlevel_limit,
91 91 otherwise it is formatted with self.highlevel_format.
92 92
93 93 Useful for adding 'WARNING' to warning messages,
94 94 without adding 'INFO' to info, etc.
95 95 """
96 96 highlevel_limit = logging.WARN
97 97 highlevel_format = " %(levelname)s |"
98 98
99 99 def format(self, record):
100 100 if record.levelno >= self.highlevel_limit:
101 101 record.highlevel = self.highlevel_format % record.__dict__
102 102 else:
103 103 record.highlevel = ""
104 104 return super(LevelFormatter, self).format(record)
105 105
106 106
107 107 class Application(SingletonConfigurable):
108 108 """A singleton application with full configuration support."""
109 109
110 110 # The name of the application, will usually match the name of the command
111 111 # line application
112 112 name = Unicode(u'application')
113 113
114 114 # The description of the application that is printed at the beginning
115 115 # of the help.
116 116 description = Unicode(u'This is an application.')
117 117 # default section descriptions
118 118 option_description = Unicode(option_description)
119 119 keyvalue_description = Unicode(keyvalue_description)
120 120 subcommand_description = Unicode(subcommand_description)
121 121
122 122 python_config_loader_class = PyFileConfigLoader
123 123 json_config_loader_class = JSONFileConfigLoader
124 124
125 125 # The usage and example string that goes at the end of the help string.
126 126 examples = Unicode()
127 127
128 128 # A sequence of Configurable subclasses whose config=True attributes will
129 129 # be exposed at the command line.
130 130 classes = []
131 131 @property
132 132 def _help_classes(self):
133 133 """Define `App.help_classes` if CLI classes should differ from config file classes"""
134 134 return getattr(self, 'help_classes', self.classes)
135 135
136 136 @property
137 137 def _config_classes(self):
138 138 """Define `App.config_classes` if config file classes should differ from CLI classes."""
139 139 return getattr(self, 'config_classes', self.classes)
140 140
141 141 # The version string of this application.
142 142 version = Unicode(u'0.0')
143 143
144 144 # the argv used to initialize the application
145 145 argv = List()
146 146
147 147 # The log level for the application
148 148 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
149 149 default_value=logging.WARN,
150 150 config=True,
151 151 help="Set the log level by value or name.")
152 152 def _log_level_changed(self, name, old, new):
153 153 """Adjust the log level when log_level is set."""
154 154 if isinstance(new, string_types):
155 155 new = getattr(logging, new)
156 156 self.log_level = new
157 157 self.log.setLevel(new)
158 158
159 159 _log_formatter_cls = LevelFormatter
160 160
161 161 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
162 162 help="The date format used by logging formatters for %(asctime)s"
163 163 )
164 164 def _log_datefmt_changed(self, name, old, new):
165 165 self._log_format_changed('log_format', self.log_format, self.log_format)
166 166
167 167 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
168 168 help="The Logging format template",
169 169 )
170 170 def _log_format_changed(self, name, old, new):
171 171 """Change the log formatter when log_format is set."""
172 172 _log_handler = self.log.handlers[0]
173 173 _log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt)
174 174 _log_handler.setFormatter(_log_formatter)
175 175
176 176
177 log = Instance(logging.Logger)
177 log = Instance(logging.Logger, allow_none=True)
178 178 def _log_default(self):
179 179 """Start logging for this application.
180 180
181 181 The default is to log to stderr using a StreamHandler, if no default
182 182 handler already exists. The log level starts at logging.WARN, but this
183 183 can be adjusted by setting the ``log_level`` attribute.
184 184 """
185 185 log = logging.getLogger(self.__class__.__name__)
186 186 log.setLevel(self.log_level)
187 187 log.propagate = False
188 188 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
189 189 while _log:
190 190 if _log.handlers:
191 191 return log
192 192 if not _log.propagate:
193 193 break
194 194 else:
195 195 _log = _log.parent
196 196 if sys.executable.endswith('pythonw.exe'):
197 197 # this should really go to a file, but file-logging is only
198 198 # hooked up in parallel applications
199 199 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
200 200 else:
201 201 _log_handler = logging.StreamHandler()
202 202 _log_formatter = self._log_formatter_cls(fmt=self.log_format, datefmt=self.log_datefmt)
203 203 _log_handler.setFormatter(_log_formatter)
204 204 log.addHandler(_log_handler)
205 205 return log
206 206
207 207 # the alias map for configurables
208 208 aliases = Dict({'log-level' : 'Application.log_level'})
209 209
210 210 # flags for loading Configurables or store_const style flags
211 211 # flags are loaded from this dict by '--key' flags
212 212 # this must be a dict of two-tuples, the first element being the Config/dict
213 213 # and the second being the help string for the flag
214 214 flags = Dict()
215 215 def _flags_changed(self, name, old, new):
216 216 """ensure flags dict is valid"""
217 217 for key,value in iteritems(new):
218 218 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
219 219 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
220 220 assert isinstance(value[1], string_types), "Bad flag: %r:%s"%(key,value)
221 221
222 222
223 223 # subcommands for launching other applications
224 224 # if this is not empty, this will be a parent Application
225 225 # this must be a dict of two-tuples,
226 226 # the first element being the application class/import string
227 227 # and the second being the help string for the subcommand
228 228 subcommands = Dict()
229 229 # parse_command_line will initialize a subapp, if requested
230 230 subapp = Instance('IPython.config.application.Application', allow_none=True)
231 231
232 232 # extra command-line arguments that don't set config values
233 233 extra_args = List(Unicode)
234 234
235 235
236 236 def __init__(self, **kwargs):
237 237 SingletonConfigurable.__init__(self, **kwargs)
238 238 # Ensure my class is in self.classes, so my attributes appear in command line
239 239 # options and config files.
240 240 if self.__class__ not in self.classes:
241 241 self.classes.insert(0, self.__class__)
242 242
243 243 def _config_changed(self, name, old, new):
244 244 SingletonConfigurable._config_changed(self, name, old, new)
245 245 self.log.debug('Config changed:')
246 246 self.log.debug(repr(new))
247 247
248 248 @catch_config_error
249 249 def initialize(self, argv=None):
250 250 """Do the basic steps to configure me.
251 251
252 252 Override in subclasses.
253 253 """
254 254 self.parse_command_line(argv)
255 255
256 256
257 257 def start(self):
258 258 """Start the app mainloop.
259 259
260 260 Override in subclasses.
261 261 """
262 262 if self.subapp is not None:
263 263 return self.subapp.start()
264 264
265 265 def print_alias_help(self):
266 266 """Print the alias part of the help."""
267 267 if not self.aliases:
268 268 return
269 269
270 270 lines = []
271 271 classdict = {}
272 272 for cls in self._help_classes:
273 273 # include all parents (up to, but excluding Configurable) in available names
274 274 for c in cls.mro()[:-3]:
275 275 classdict[c.__name__] = c
276 276
277 277 for alias, longname in iteritems(self.aliases):
278 278 classname, traitname = longname.split('.',1)
279 279 cls = classdict[classname]
280 280
281 281 trait = cls.class_traits(config=True)[traitname]
282 282 help = cls.class_get_trait_help(trait).splitlines()
283 283 # reformat first line
284 284 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
285 285 if len(alias) == 1:
286 286 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
287 287 lines.extend(help)
288 288 # lines.append('')
289 289 print(os.linesep.join(lines))
290 290
291 291 def print_flag_help(self):
292 292 """Print the flag part of the help."""
293 293 if not self.flags:
294 294 return
295 295
296 296 lines = []
297 297 for m, (cfg,help) in iteritems(self.flags):
298 298 prefix = '--' if len(m) > 1 else '-'
299 299 lines.append(prefix+m)
300 300 lines.append(indent(dedent(help.strip())))
301 301 # lines.append('')
302 302 print(os.linesep.join(lines))
303 303
304 304 def print_options(self):
305 305 if not self.flags and not self.aliases:
306 306 return
307 307 lines = ['Options']
308 308 lines.append('-'*len(lines[0]))
309 309 lines.append('')
310 310 for p in wrap_paragraphs(self.option_description):
311 311 lines.append(p)
312 312 lines.append('')
313 313 print(os.linesep.join(lines))
314 314 self.print_flag_help()
315 315 self.print_alias_help()
316 316 print()
317 317
318 318 def print_subcommands(self):
319 319 """Print the subcommand part of the help."""
320 320 if not self.subcommands:
321 321 return
322 322
323 323 lines = ["Subcommands"]
324 324 lines.append('-'*len(lines[0]))
325 325 lines.append('')
326 326 for p in wrap_paragraphs(self.subcommand_description.format(
327 327 app=self.name)):
328 328 lines.append(p)
329 329 lines.append('')
330 330 for subc, (cls, help) in iteritems(self.subcommands):
331 331 lines.append(subc)
332 332 if help:
333 333 lines.append(indent(dedent(help.strip())))
334 334 lines.append('')
335 335 print(os.linesep.join(lines))
336 336
337 337 def print_help(self, classes=False):
338 338 """Print the help for each Configurable class in self.classes.
339 339
340 340 If classes=False (the default), only flags and aliases are printed.
341 341 """
342 342 self.print_description()
343 343 self.print_subcommands()
344 344 self.print_options()
345 345
346 346 if classes:
347 347 help_classes = self._help_classes
348 348 if help_classes:
349 349 print("Class parameters")
350 350 print("----------------")
351 351 print()
352 352 for p in wrap_paragraphs(self.keyvalue_description):
353 353 print(p)
354 354 print()
355 355
356 356 for cls in help_classes:
357 357 cls.class_print_help()
358 358 print()
359 359 else:
360 360 print("To see all available configurables, use `--help-all`")
361 361 print()
362 362
363 363 self.print_examples()
364 364
365 365
366 366 def print_description(self):
367 367 """Print the application description."""
368 368 for p in wrap_paragraphs(self.description):
369 369 print(p)
370 370 print()
371 371
372 372 def print_examples(self):
373 373 """Print usage and examples.
374 374
375 375 This usage string goes at the end of the command line help string
376 376 and should contain examples of the application's usage.
377 377 """
378 378 if self.examples:
379 379 print("Examples")
380 380 print("--------")
381 381 print()
382 382 print(indent(dedent(self.examples.strip())))
383 383 print()
384 384
385 385 def print_version(self):
386 386 """Print the version string."""
387 387 print(self.version)
388 388
389 389 def update_config(self, config):
390 390 """Fire the traits events when the config is updated."""
391 391 # Save a copy of the current config.
392 392 newconfig = deepcopy(self.config)
393 393 # Merge the new config into the current one.
394 394 newconfig.merge(config)
395 395 # Save the combined config as self.config, which triggers the traits
396 396 # events.
397 397 self.config = newconfig
398 398
399 399 @catch_config_error
400 400 def initialize_subcommand(self, subc, argv=None):
401 401 """Initialize a subcommand with argv."""
402 402 subapp,help = self.subcommands.get(subc)
403 403
404 404 if isinstance(subapp, string_types):
405 405 subapp = import_item(subapp)
406 406
407 407 # clear existing instances
408 408 self.__class__.clear_instance()
409 409 # instantiate
410 410 self.subapp = subapp.instance(config=self.config)
411 411 # and initialize subapp
412 412 self.subapp.initialize(argv)
413 413
414 414 def flatten_flags(self):
415 415 """flatten flags and aliases, so cl-args override as expected.
416 416
417 417 This prevents issues such as an alias pointing to InteractiveShell,
418 418 but a config file setting the same trait in TerminalInteraciveShell
419 419 getting inappropriate priority over the command-line arg.
420 420
421 421 Only aliases with exactly one descendent in the class list
422 422 will be promoted.
423 423
424 424 """
425 425 # build a tree of classes in our list that inherit from a particular
426 426 # it will be a dict by parent classname of classes in our list
427 427 # that are descendents
428 428 mro_tree = defaultdict(list)
429 429 for cls in self._help_classes:
430 430 clsname = cls.__name__
431 431 for parent in cls.mro()[1:-3]:
432 432 # exclude cls itself and Configurable,HasTraits,object
433 433 mro_tree[parent.__name__].append(clsname)
434 434 # flatten aliases, which have the form:
435 435 # { 'alias' : 'Class.trait' }
436 436 aliases = {}
437 437 for alias, cls_trait in iteritems(self.aliases):
438 438 cls,trait = cls_trait.split('.',1)
439 439 children = mro_tree[cls]
440 440 if len(children) == 1:
441 441 # exactly one descendent, promote alias
442 442 cls = children[0]
443 443 aliases[alias] = '.'.join([cls,trait])
444 444
445 445 # flatten flags, which are of the form:
446 446 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
447 447 flags = {}
448 448 for key, (flagdict, help) in iteritems(self.flags):
449 449 newflag = {}
450 450 for cls, subdict in iteritems(flagdict):
451 451 children = mro_tree[cls]
452 452 # exactly one descendent, promote flag section
453 453 if len(children) == 1:
454 454 cls = children[0]
455 455 newflag[cls] = subdict
456 456 flags[key] = (newflag, help)
457 457 return flags, aliases
458 458
459 459 @catch_config_error
460 460 def parse_command_line(self, argv=None):
461 461 """Parse the command line arguments."""
462 462 argv = sys.argv[1:] if argv is None else argv
463 463 self.argv = [ py3compat.cast_unicode(arg) for arg in argv ]
464 464
465 465 if argv and argv[0] == 'help':
466 466 # turn `ipython help notebook` into `ipython notebook -h`
467 467 argv = argv[1:] + ['-h']
468 468
469 469 if self.subcommands and len(argv) > 0:
470 470 # we have subcommands, and one may have been specified
471 471 subc, subargv = argv[0], argv[1:]
472 472 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
473 473 # it's a subcommand, and *not* a flag or class parameter
474 474 return self.initialize_subcommand(subc, subargv)
475 475
476 476 # Arguments after a '--' argument are for the script IPython may be
477 477 # about to run, not IPython iteslf. For arguments parsed here (help and
478 478 # version), we want to only search the arguments up to the first
479 479 # occurrence of '--', which we're calling interpreted_argv.
480 480 try:
481 481 interpreted_argv = argv[:argv.index('--')]
482 482 except ValueError:
483 483 interpreted_argv = argv
484 484
485 485 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
486 486 self.print_help('--help-all' in interpreted_argv)
487 487 self.exit(0)
488 488
489 489 if '--version' in interpreted_argv or '-V' in interpreted_argv:
490 490 self.print_version()
491 491 self.exit(0)
492 492
493 493 # flatten flags&aliases, so cl-args get appropriate priority:
494 494 flags,aliases = self.flatten_flags()
495 495 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
496 496 flags=flags, log=self.log)
497 497 config = loader.load_config()
498 498 self.update_config(config)
499 499 # store unparsed args in extra_args
500 500 self.extra_args = loader.extra_args
501 501
502 502 @classmethod
503 503 def _load_config_files(cls, basefilename, path=None, log=None):
504 504 """Load config files (py,json) by filename and path.
505 505
506 506 yield each config object in turn.
507 507 """
508 508
509 509 if not isinstance(path, list):
510 510 path = [path]
511 511 for path in path[::-1]:
512 512 # path list is in descending priority order, so load files backwards:
513 513 pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
514 514 jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
515 515 config = None
516 516 for loader in [pyloader, jsonloader]:
517 517 try:
518 518 config = loader.load_config()
519 519 except ConfigFileNotFound:
520 520 pass
521 521 except Exception:
522 522 # try to get the full filename, but it will be empty in the
523 523 # unlikely event that the error raised before filefind finished
524 524 filename = loader.full_filename or basefilename
525 525 # problem while running the file
526 526 if log:
527 527 log.error("Exception while loading config file %s",
528 528 filename, exc_info=True)
529 529 else:
530 530 if log:
531 531 log.debug("Loaded config file: %s", loader.full_filename)
532 532 if config:
533 533 yield config
534 534
535 535 raise StopIteration
536 536
537 537
538 538 @catch_config_error
539 539 def load_config_file(self, filename, path=None):
540 540 """Load config files by filename and path."""
541 541 filename, ext = os.path.splitext(filename)
542 542 loaded = []
543 543 for config in self._load_config_files(filename, path=path, log=self.log):
544 544 loaded.append(config)
545 545 self.update_config(config)
546 546 if len(loaded) > 1:
547 547 collisions = loaded[0].collisions(loaded[1])
548 548 if collisions:
549 549 self.log.warn("Collisions detected in {0}.py and {0}.json config files."
550 550 " {0}.json has higher priority: {1}".format(
551 551 filename, json.dumps(collisions, indent=2),
552 552 ))
553 553
554 554
555 555 def generate_config_file(self):
556 556 """generate default config file from Configurables"""
557 557 lines = ["# Configuration file for %s." % self.name]
558 558 lines.append('')
559 559 for cls in self._config_classes:
560 560 lines.append(cls.class_config_section())
561 561 return '\n'.join(lines)
562 562
563 563 def exit(self, exit_status=0):
564 564 self.log.debug("Exiting application: %s" % self.name)
565 565 sys.exit(exit_status)
566 566
567 567 @classmethod
568 568 def launch_instance(cls, argv=None, **kwargs):
569 569 """Launch a global instance of this Application
570 570
571 571 If a global instance already exists, this reinitializes and starts it
572 572 """
573 573 app = cls.instance(**kwargs)
574 574 app.initialize(argv)
575 575 app.start()
576 576
577 577 #-----------------------------------------------------------------------------
578 578 # utility functions, for convenience
579 579 #-----------------------------------------------------------------------------
580 580
581 581 def boolean_flag(name, configurable, set_help='', unset_help=''):
582 582 """Helper for building basic --trait, --no-trait flags.
583 583
584 584 Parameters
585 585 ----------
586 586
587 587 name : str
588 588 The name of the flag.
589 589 configurable : str
590 590 The 'Class.trait' string of the trait to be set/unset with the flag
591 591 set_help : unicode
592 592 help string for --name flag
593 593 unset_help : unicode
594 594 help string for --no-name flag
595 595
596 596 Returns
597 597 -------
598 598
599 599 cfg : dict
600 600 A dict with two keys: 'name', and 'no-name', for setting and unsetting
601 601 the trait, respectively.
602 602 """
603 603 # default helpstrings
604 604 set_help = set_help or "set %s=True"%configurable
605 605 unset_help = unset_help or "set %s=False"%configurable
606 606
607 607 cls,trait = configurable.split('.')
608 608
609 609 setter = {cls : {trait : True}}
610 610 unsetter = {cls : {trait : False}}
611 611 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
612 612
613 613
614 614 def get_config():
615 615 """Get the config object for the global Application instance, if there is one
616 616
617 617 otherwise return an empty config object
618 618 """
619 619 if Application.initialized():
620 620 return Application.instance().config
621 621 else:
622 622 return Config()
@@ -1,380 +1,380 b''
1 1 # encoding: utf-8
2 2 """A base class for objects that are configurable."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import logging
10 10 from copy import deepcopy
11 11
12 12 from .loader import Config, LazyConfigValue
13 13 from IPython.utils.traitlets import HasTraits, Instance
14 14 from IPython.utils.text import indent, wrap_paragraphs
15 15 from IPython.utils.py3compat import iteritems
16 16
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Helper classes for Configurables
20 20 #-----------------------------------------------------------------------------
21 21
22 22
23 23 class ConfigurableError(Exception):
24 24 pass
25 25
26 26
27 27 class MultipleInstanceError(ConfigurableError):
28 28 pass
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Configurable implementation
32 32 #-----------------------------------------------------------------------------
33 33
34 34 class Configurable(HasTraits):
35 35
36 36 config = Instance(Config, (), {})
37 parent = Instance('IPython.config.configurable.Configurable')
37 parent = Instance('IPython.config.configurable.Configurable', allow_none=True)
38 38
39 39 def __init__(self, **kwargs):
40 40 """Create a configurable given a config config.
41 41
42 42 Parameters
43 43 ----------
44 44 config : Config
45 45 If this is empty, default values are used. If config is a
46 46 :class:`Config` instance, it will be used to configure the
47 47 instance.
48 48 parent : Configurable instance, optional
49 49 The parent Configurable instance of this object.
50 50
51 51 Notes
52 52 -----
53 53 Subclasses of Configurable must call the :meth:`__init__` method of
54 54 :class:`Configurable` *before* doing anything else and using
55 55 :func:`super`::
56 56
57 57 class MyConfigurable(Configurable):
58 58 def __init__(self, config=None):
59 59 super(MyConfigurable, self).__init__(config=config)
60 60 # Then any other code you need to finish initialization.
61 61
62 62 This ensures that instances will be configured properly.
63 63 """
64 64 parent = kwargs.pop('parent', None)
65 65 if parent is not None:
66 66 # config is implied from parent
67 67 if kwargs.get('config', None) is None:
68 68 kwargs['config'] = parent.config
69 69 self.parent = parent
70 70
71 71 config = kwargs.pop('config', None)
72 72
73 73 # load kwarg traits, other than config
74 74 super(Configurable, self).__init__(**kwargs)
75 75
76 76 # load config
77 77 if config is not None:
78 78 # We used to deepcopy, but for now we are trying to just save
79 79 # by reference. This *could* have side effects as all components
80 80 # will share config. In fact, I did find such a side effect in
81 81 # _config_changed below. If a config attribute value was a mutable type
82 82 # all instances of a component were getting the same copy, effectively
83 83 # making that a class attribute.
84 84 # self.config = deepcopy(config)
85 85 self.config = config
86 86 else:
87 87 # allow _config_default to return something
88 88 self._load_config(self.config)
89 89
90 90 # Ensure explicit kwargs are applied after loading config.
91 91 # This is usually redundant, but ensures config doesn't override
92 92 # explicitly assigned values.
93 93 for key, value in kwargs.items():
94 94 setattr(self, key, value)
95 95
96 96 #-------------------------------------------------------------------------
97 97 # Static trait notifiations
98 98 #-------------------------------------------------------------------------
99 99
100 100 @classmethod
101 101 def section_names(cls):
102 102 """return section names as a list"""
103 103 return [c.__name__ for c in reversed(cls.__mro__) if
104 104 issubclass(c, Configurable) and issubclass(cls, c)
105 105 ]
106 106
107 107 def _find_my_config(self, cfg):
108 108 """extract my config from a global Config object
109 109
110 110 will construct a Config object of only the config values that apply to me
111 111 based on my mro(), as well as those of my parent(s) if they exist.
112 112
113 113 If I am Bar and my parent is Foo, and their parent is Tim,
114 114 this will return merge following config sections, in this order::
115 115
116 116 [Bar, Foo.bar, Tim.Foo.Bar]
117 117
118 118 With the last item being the highest priority.
119 119 """
120 120 cfgs = [cfg]
121 121 if self.parent:
122 122 cfgs.append(self.parent._find_my_config(cfg))
123 123 my_config = Config()
124 124 for c in cfgs:
125 125 for sname in self.section_names():
126 126 # Don't do a blind getattr as that would cause the config to
127 127 # dynamically create the section with name Class.__name__.
128 128 if c._has_section(sname):
129 129 my_config.merge(c[sname])
130 130 return my_config
131 131
132 132 def _load_config(self, cfg, section_names=None, traits=None):
133 133 """load traits from a Config object"""
134 134
135 135 if traits is None:
136 136 traits = self.traits(config=True)
137 137 if section_names is None:
138 138 section_names = self.section_names()
139 139
140 140 my_config = self._find_my_config(cfg)
141 141
142 142 # hold trait notifications until after all config has been loaded
143 143 with self.hold_trait_notifications():
144 144 for name, config_value in iteritems(my_config):
145 145 if name in traits:
146 146 if isinstance(config_value, LazyConfigValue):
147 147 # ConfigValue is a wrapper for using append / update on containers
148 148 # without having to copy the initial value
149 149 initial = getattr(self, name)
150 150 config_value = config_value.get_value(initial)
151 151 # We have to do a deepcopy here if we don't deepcopy the entire
152 152 # config object. If we don't, a mutable config_value will be
153 153 # shared by all instances, effectively making it a class attribute.
154 154 setattr(self, name, deepcopy(config_value))
155 155
156 156 def _config_changed(self, name, old, new):
157 157 """Update all the class traits having ``config=True`` as metadata.
158 158
159 159 For any class trait with a ``config`` metadata attribute that is
160 160 ``True``, we update the trait with the value of the corresponding
161 161 config entry.
162 162 """
163 163 # Get all traits with a config metadata entry that is True
164 164 traits = self.traits(config=True)
165 165
166 166 # We auto-load config section for this class as well as any parent
167 167 # classes that are Configurable subclasses. This starts with Configurable
168 168 # and works down the mro loading the config for each section.
169 169 section_names = self.section_names()
170 170 self._load_config(new, traits=traits, section_names=section_names)
171 171
172 172 def update_config(self, config):
173 173 """Fire the traits events when the config is updated."""
174 174 # Save a copy of the current config.
175 175 newconfig = deepcopy(self.config)
176 176 # Merge the new config into the current one.
177 177 newconfig.merge(config)
178 178 # Save the combined config as self.config, which triggers the traits
179 179 # events.
180 180 self.config = newconfig
181 181
182 182 @classmethod
183 183 def class_get_help(cls, inst=None):
184 184 """Get the help string for this class in ReST format.
185 185
186 186 If `inst` is given, it's current trait values will be used in place of
187 187 class defaults.
188 188 """
189 189 assert inst is None or isinstance(inst, cls)
190 190 final_help = []
191 191 final_help.append(u'%s options' % cls.__name__)
192 192 final_help.append(len(final_help[0])*u'-')
193 193 for k, v in sorted(cls.class_traits(config=True).items()):
194 194 help = cls.class_get_trait_help(v, inst)
195 195 final_help.append(help)
196 196 return '\n'.join(final_help)
197 197
198 198 @classmethod
199 199 def class_get_trait_help(cls, trait, inst=None):
200 200 """Get the help string for a single trait.
201 201
202 202 If `inst` is given, it's current trait values will be used in place of
203 203 the class default.
204 204 """
205 205 assert inst is None or isinstance(inst, cls)
206 206 lines = []
207 207 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
208 208 lines.append(header)
209 209 if inst is not None:
210 210 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
211 211 else:
212 212 try:
213 213 dvr = repr(trait.get_default_value())
214 214 except Exception:
215 215 dvr = None # ignore defaults we can't construct
216 216 if dvr is not None:
217 217 if len(dvr) > 64:
218 218 dvr = dvr[:61]+'...'
219 219 lines.append(indent('Default: %s' % dvr, 4))
220 220 if 'Enum' in trait.__class__.__name__:
221 221 # include Enum choices
222 222 lines.append(indent('Choices: %r' % (trait.values,)))
223 223
224 224 help = trait.get_metadata('help')
225 225 if help is not None:
226 226 help = '\n'.join(wrap_paragraphs(help, 76))
227 227 lines.append(indent(help, 4))
228 228 return '\n'.join(lines)
229 229
230 230 @classmethod
231 231 def class_print_help(cls, inst=None):
232 232 """Get the help string for a single trait and print it."""
233 233 print(cls.class_get_help(inst))
234 234
235 235 @classmethod
236 236 def class_config_section(cls):
237 237 """Get the config class config section"""
238 238 def c(s):
239 239 """return a commented, wrapped block."""
240 240 s = '\n\n'.join(wrap_paragraphs(s, 78))
241 241
242 242 return '# ' + s.replace('\n', '\n# ')
243 243
244 244 # section header
245 245 breaker = '#' + '-'*78
246 246 s = "# %s configuration" % cls.__name__
247 247 lines = [breaker, s, breaker, '']
248 248 # get the description trait
249 249 desc = cls.class_traits().get('description')
250 250 if desc:
251 251 desc = desc.default_value
252 252 else:
253 253 # no description trait, use __doc__
254 254 desc = getattr(cls, '__doc__', '')
255 255 if desc:
256 256 lines.append(c(desc))
257 257 lines.append('')
258 258
259 259 parents = []
260 260 for parent in cls.mro():
261 261 # only include parents that are not base classes
262 262 # and are not the class itself
263 263 # and have some configurable traits to inherit
264 264 if parent is not cls and issubclass(parent, Configurable) and \
265 265 parent.class_traits(config=True):
266 266 parents.append(parent)
267 267
268 268 if parents:
269 269 pstr = ', '.join([ p.__name__ for p in parents ])
270 270 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
271 271 lines.append('')
272 272
273 273 for name, trait in iteritems(cls.class_traits(config=True)):
274 274 help = trait.get_metadata('help') or ''
275 275 lines.append(c(help))
276 276 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
277 277 lines.append('')
278 278 return '\n'.join(lines)
279 279
280 280
281 281
282 282 class SingletonConfigurable(Configurable):
283 283 """A configurable that only allows one instance.
284 284
285 285 This class is for classes that should only have one instance of itself
286 286 or *any* subclass. To create and retrieve such a class use the
287 287 :meth:`SingletonConfigurable.instance` method.
288 288 """
289 289
290 290 _instance = None
291 291
292 292 @classmethod
293 293 def _walk_mro(cls):
294 294 """Walk the cls.mro() for parent classes that are also singletons
295 295
296 296 For use in instance()
297 297 """
298 298
299 299 for subclass in cls.mro():
300 300 if issubclass(cls, subclass) and \
301 301 issubclass(subclass, SingletonConfigurable) and \
302 302 subclass != SingletonConfigurable:
303 303 yield subclass
304 304
305 305 @classmethod
306 306 def clear_instance(cls):
307 307 """unset _instance for this class and singleton parents.
308 308 """
309 309 if not cls.initialized():
310 310 return
311 311 for subclass in cls._walk_mro():
312 312 if isinstance(subclass._instance, cls):
313 313 # only clear instances that are instances
314 314 # of the calling class
315 315 subclass._instance = None
316 316
317 317 @classmethod
318 318 def instance(cls, *args, **kwargs):
319 319 """Returns a global instance of this class.
320 320
321 321 This method create a new instance if none have previously been created
322 322 and returns a previously created instance is one already exists.
323 323
324 324 The arguments and keyword arguments passed to this method are passed
325 325 on to the :meth:`__init__` method of the class upon instantiation.
326 326
327 327 Examples
328 328 --------
329 329
330 330 Create a singleton class using instance, and retrieve it::
331 331
332 332 >>> from IPython.config.configurable import SingletonConfigurable
333 333 >>> class Foo(SingletonConfigurable): pass
334 334 >>> foo = Foo.instance()
335 335 >>> foo == Foo.instance()
336 336 True
337 337
338 338 Create a subclass that is retrived using the base class instance::
339 339
340 340 >>> class Bar(SingletonConfigurable): pass
341 341 >>> class Bam(Bar): pass
342 342 >>> bam = Bam.instance()
343 343 >>> bam == Bar.instance()
344 344 True
345 345 """
346 346 # Create and save the instance
347 347 if cls._instance is None:
348 348 inst = cls(*args, **kwargs)
349 349 # Now make sure that the instance will also be returned by
350 350 # parent classes' _instance attribute.
351 351 for subclass in cls._walk_mro():
352 352 subclass._instance = inst
353 353
354 354 if isinstance(cls._instance, cls):
355 355 return cls._instance
356 356 else:
357 357 raise MultipleInstanceError(
358 358 'Multiple incompatible subclass instances of '
359 359 '%s are being created.' % cls.__name__
360 360 )
361 361
362 362 @classmethod
363 363 def initialized(cls):
364 364 """Has an instance been created?"""
365 365 return hasattr(cls, "_instance") and cls._instance is not None
366 366
367 367
368 368 class LoggingConfigurable(Configurable):
369 369 """A parent class for Configurables that log.
370 370
371 371 Subclasses have a log trait, and the default behavior
372 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 376 def _log_default(self):
377 377 from IPython.utils import log
378 378 return log.get_logger()
379 379
380 380
@@ -1,256 +1,256 b''
1 1 # encoding: utf-8
2 2 """
3 3 System command aliases.
4 4
5 5 Authors:
6 6
7 7 * Fernando Perez
8 8 * Brian Granger
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License.
15 15 #
16 16 # The full license is in the file COPYING.txt, distributed with this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import re
25 25 import sys
26 26
27 27 from IPython.config.configurable import Configurable
28 28 from IPython.core.error import UsageError
29 29
30 30 from IPython.utils.py3compat import string_types
31 31 from IPython.utils.traitlets import List, Instance
32 32 from IPython.utils.warn import error
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Utilities
36 36 #-----------------------------------------------------------------------------
37 37
38 38 # This is used as the pattern for calls to split_user_input.
39 39 shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
40 40
41 41 def default_aliases():
42 42 """Return list of shell aliases to auto-define.
43 43 """
44 44 # Note: the aliases defined here should be safe to use on a kernel
45 45 # regardless of what frontend it is attached to. Frontends that use a
46 46 # kernel in-process can define additional aliases that will only work in
47 47 # their case. For example, things like 'less' or 'clear' that manipulate
48 48 # the terminal should NOT be declared here, as they will only work if the
49 49 # kernel is running inside a true terminal, and not over the network.
50 50
51 51 if os.name == 'posix':
52 52 default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
53 53 ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
54 54 ('cat', 'cat'),
55 55 ]
56 56 # Useful set of ls aliases. The GNU and BSD options are a little
57 57 # different, so we make aliases that provide as similar as possible
58 58 # behavior in ipython, by passing the right flags for each platform
59 59 if sys.platform.startswith('linux'):
60 60 ls_aliases = [('ls', 'ls -F --color'),
61 61 # long ls
62 62 ('ll', 'ls -F -o --color'),
63 63 # ls normal files only
64 64 ('lf', 'ls -F -o --color %l | grep ^-'),
65 65 # ls symbolic links
66 66 ('lk', 'ls -F -o --color %l | grep ^l'),
67 67 # directories or links to directories,
68 68 ('ldir', 'ls -F -o --color %l | grep /$'),
69 69 # things which are executable
70 70 ('lx', 'ls -F -o --color %l | grep ^-..x'),
71 71 ]
72 72 elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
73 73 # OpenBSD, NetBSD. The ls implementation on these platforms do not support
74 74 # the -G switch and lack the ability to use colorized output.
75 75 ls_aliases = [('ls', 'ls -F'),
76 76 # long ls
77 77 ('ll', 'ls -F -l'),
78 78 # ls normal files only
79 79 ('lf', 'ls -F -l %l | grep ^-'),
80 80 # ls symbolic links
81 81 ('lk', 'ls -F -l %l | grep ^l'),
82 82 # directories or links to directories,
83 83 ('ldir', 'ls -F -l %l | grep /$'),
84 84 # things which are executable
85 85 ('lx', 'ls -F -l %l | grep ^-..x'),
86 86 ]
87 87 else:
88 88 # BSD, OSX, etc.
89 89 ls_aliases = [('ls', 'ls -F -G'),
90 90 # long ls
91 91 ('ll', 'ls -F -l -G'),
92 92 # ls normal files only
93 93 ('lf', 'ls -F -l -G %l | grep ^-'),
94 94 # ls symbolic links
95 95 ('lk', 'ls -F -l -G %l | grep ^l'),
96 96 # directories or links to directories,
97 97 ('ldir', 'ls -F -G -l %l | grep /$'),
98 98 # things which are executable
99 99 ('lx', 'ls -F -l -G %l | grep ^-..x'),
100 100 ]
101 101 default_aliases = default_aliases + ls_aliases
102 102 elif os.name in ['nt', 'dos']:
103 103 default_aliases = [('ls', 'dir /on'),
104 104 ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
105 105 ('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
106 106 ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
107 107 ]
108 108 else:
109 109 default_aliases = []
110 110
111 111 return default_aliases
112 112
113 113
114 114 class AliasError(Exception):
115 115 pass
116 116
117 117
118 118 class InvalidAliasError(AliasError):
119 119 pass
120 120
121 121 class Alias(object):
122 122 """Callable object storing the details of one alias.
123 123
124 124 Instances are registered as magic functions to allow use of aliases.
125 125 """
126 126
127 127 # Prepare blacklist
128 128 blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
129 129
130 130 def __init__(self, shell, name, cmd):
131 131 self.shell = shell
132 132 self.name = name
133 133 self.cmd = cmd
134 134 self.nargs = self.validate()
135 135
136 136 def validate(self):
137 137 """Validate the alias, and return the number of arguments."""
138 138 if self.name in self.blacklist:
139 139 raise InvalidAliasError("The name %s can't be aliased "
140 140 "because it is a keyword or builtin." % self.name)
141 141 try:
142 142 caller = self.shell.magics_manager.magics['line'][self.name]
143 143 except KeyError:
144 144 pass
145 145 else:
146 146 if not isinstance(caller, Alias):
147 147 raise InvalidAliasError("The name %s can't be aliased "
148 148 "because it is another magic command." % self.name)
149 149
150 150 if not (isinstance(self.cmd, string_types)):
151 151 raise InvalidAliasError("An alias command must be a string, "
152 152 "got: %r" % self.cmd)
153 153
154 154 nargs = self.cmd.count('%s') - self.cmd.count('%%s')
155 155
156 156 if (nargs > 0) and (self.cmd.find('%l') >= 0):
157 157 raise InvalidAliasError('The %s and %l specifiers are mutually '
158 158 'exclusive in alias definitions.')
159 159
160 160 return nargs
161 161
162 162 def __repr__(self):
163 163 return "<alias {} for {!r}>".format(self.name, self.cmd)
164 164
165 165 def __call__(self, rest=''):
166 166 cmd = self.cmd
167 167 nargs = self.nargs
168 168 # Expand the %l special to be the user's input line
169 169 if cmd.find('%l') >= 0:
170 170 cmd = cmd.replace('%l', rest)
171 171 rest = ''
172 172
173 173 if nargs==0:
174 174 if cmd.find('%%s') >= 1:
175 175 cmd = cmd.replace('%%s', '%s')
176 176 # Simple, argument-less aliases
177 177 cmd = '%s %s' % (cmd, rest)
178 178 else:
179 179 # Handle aliases with positional arguments
180 180 args = rest.split(None, nargs)
181 181 if len(args) < nargs:
182 182 raise UsageError('Alias <%s> requires %s arguments, %s given.' %
183 183 (self.name, nargs, len(args)))
184 184 cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
185 185
186 186 self.shell.system(cmd)
187 187
188 188 #-----------------------------------------------------------------------------
189 189 # Main AliasManager class
190 190 #-----------------------------------------------------------------------------
191 191
192 192 class AliasManager(Configurable):
193 193
194 194 default_aliases = List(default_aliases(), config=True)
195 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 198 def __init__(self, shell=None, **kwargs):
199 199 super(AliasManager, self).__init__(shell=shell, **kwargs)
200 200 # For convenient access
201 201 self.linemagics = self.shell.magics_manager.magics['line']
202 202 self.init_aliases()
203 203
204 204 def init_aliases(self):
205 205 # Load default & user aliases
206 206 for name, cmd in self.default_aliases + self.user_aliases:
207 207 self.soft_define_alias(name, cmd)
208 208
209 209 @property
210 210 def aliases(self):
211 211 return [(n, func.cmd) for (n, func) in self.linemagics.items()
212 212 if isinstance(func, Alias)]
213 213
214 214 def soft_define_alias(self, name, cmd):
215 215 """Define an alias, but don't raise on an AliasError."""
216 216 try:
217 217 self.define_alias(name, cmd)
218 218 except AliasError as e:
219 219 error("Invalid alias: %s" % e)
220 220
221 221 def define_alias(self, name, cmd):
222 222 """Define a new alias after validating it.
223 223
224 224 This will raise an :exc:`AliasError` if there are validation
225 225 problems.
226 226 """
227 227 caller = Alias(shell=self.shell, name=name, cmd=cmd)
228 228 self.shell.magics_manager.register_function(caller, magic_kind='line',
229 229 magic_name=name)
230 230
231 231 def get_alias(self, name):
232 232 """Return an alias, or None if no alias by that name exists."""
233 233 aname = self.linemagics.get(name, None)
234 234 return aname if isinstance(aname, Alias) else None
235 235
236 236 def is_alias(self, name):
237 237 """Return whether or not a given name has been defined as an alias"""
238 238 return self.get_alias(name) is not None
239 239
240 240 def undefine_alias(self, name):
241 241 if self.is_alias(name):
242 242 del self.linemagics[name]
243 243 else:
244 244 raise ValueError('%s is not an alias' % name)
245 245
246 246 def clear_aliases(self):
247 247 for name, cmd in self.aliases:
248 248 self.undefine_alias(name)
249 249
250 250 def retrieve_alias(self, name):
251 251 """Retrieve the command to which an alias expands."""
252 252 caller = self.get_alias(name)
253 253 if caller:
254 254 return caller.cmd
255 255 else:
256 256 raise ValueError('%s is not an alias' % name)
@@ -1,396 +1,396 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating configurables.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 import atexit
16 16 import glob
17 17 import logging
18 18 import os
19 19 import shutil
20 20 import sys
21 21
22 22 from IPython.config.application import Application, catch_config_error
23 23 from IPython.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 24 from IPython.core import release, crashhandler
25 25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 26 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
27 27 from IPython.utils import py3compat
28 28 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
29 29
30 30 if os.name == 'nt':
31 31 programdata = os.environ.get('PROGRAMDATA', None)
32 32 if programdata:
33 33 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
34 34 else: # PROGRAMDATA is not defined by default on XP.
35 35 SYSTEM_CONFIG_DIRS = []
36 36 else:
37 37 SYSTEM_CONFIG_DIRS = [
38 38 "/usr/local/etc/ipython",
39 39 "/etc/ipython",
40 40 ]
41 41
42 42
43 43 # aliases and flags
44 44
45 45 base_aliases = {
46 46 'profile-dir' : 'ProfileDir.location',
47 47 'profile' : 'BaseIPythonApplication.profile',
48 48 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
49 49 'log-level' : 'Application.log_level',
50 50 'config' : 'BaseIPythonApplication.extra_config_file',
51 51 }
52 52
53 53 base_flags = dict(
54 54 debug = ({'Application' : {'log_level' : logging.DEBUG}},
55 55 "set log level to logging.DEBUG (maximize logging output)"),
56 56 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
57 57 "set log level to logging.CRITICAL (minimize logging output)"),
58 58 init = ({'BaseIPythonApplication' : {
59 59 'copy_config_files' : True,
60 60 'auto_create' : True}
61 61 }, """Initialize profile with default config files. This is equivalent
62 62 to running `ipython profile create <profile>` prior to startup.
63 63 """)
64 64 )
65 65
66 66 class ProfileAwareConfigLoader(PyFileConfigLoader):
67 67 """A Python file config loader that is aware of IPython profiles."""
68 68 def load_subconfig(self, fname, path=None, profile=None):
69 69 if profile is not None:
70 70 try:
71 71 profile_dir = ProfileDir.find_profile_dir_by_name(
72 72 get_ipython_dir(),
73 73 profile,
74 74 )
75 75 except ProfileDirError:
76 76 return
77 77 path = profile_dir.location
78 78 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
79 79
80 80 class BaseIPythonApplication(Application):
81 81
82 82 name = Unicode(u'ipython')
83 83 description = Unicode(u'IPython: an enhanced interactive Python shell.')
84 84 version = Unicode(release.version)
85 85
86 86 aliases = Dict(base_aliases)
87 87 flags = Dict(base_flags)
88 88 classes = List([ProfileDir])
89 89
90 90 # enable `load_subconfig('cfg.py', profile='name')`
91 91 python_config_loader_class = ProfileAwareConfigLoader
92 92
93 93 # Track whether the config_file has changed,
94 94 # because some logic happens only if we aren't using the default.
95 95 config_file_specified = Set()
96 96
97 97 config_file_name = Unicode()
98 98 def _config_file_name_default(self):
99 99 return self.name.replace('-','_') + u'_config.py'
100 100 def _config_file_name_changed(self, name, old, new):
101 101 if new != old:
102 102 self.config_file_specified.add(new)
103 103
104 104 # The directory that contains IPython's builtin profiles.
105 105 builtin_profile_dir = Unicode(
106 106 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
107 107 )
108 108
109 109 config_file_paths = List(Unicode)
110 110 def _config_file_paths_default(self):
111 111 return [py3compat.getcwd()]
112 112
113 113 extra_config_file = Unicode(config=True,
114 114 help="""Path to an extra config file to load.
115 115
116 116 If specified, load this config file in addition to any other IPython config.
117 117 """)
118 118 def _extra_config_file_changed(self, name, old, new):
119 119 try:
120 120 self.config_files.remove(old)
121 121 except ValueError:
122 122 pass
123 123 self.config_file_specified.add(new)
124 124 self.config_files.append(new)
125 125
126 126 profile = Unicode(u'default', config=True,
127 127 help="""The IPython profile to use."""
128 128 )
129 129
130 130 def _profile_changed(self, name, old, new):
131 131 self.builtin_profile_dir = os.path.join(
132 132 get_ipython_package_dir(), u'config', u'profile', new
133 133 )
134 134
135 135 ipython_dir = Unicode(config=True,
136 136 help="""
137 137 The name of the IPython directory. This directory is used for logging
138 138 configuration (through profiles), history storage, etc. The default
139 139 is usually $HOME/.ipython. This option can also be specified through
140 140 the environment variable IPYTHONDIR.
141 141 """
142 142 )
143 143 def _ipython_dir_default(self):
144 144 d = get_ipython_dir()
145 145 self._ipython_dir_changed('ipython_dir', d, d)
146 146 return d
147 147
148 148 _in_init_profile_dir = False
149 profile_dir = Instance(ProfileDir)
149 profile_dir = Instance(ProfileDir, allow_none=True)
150 150 def _profile_dir_default(self):
151 151 # avoid recursion
152 152 if self._in_init_profile_dir:
153 153 return
154 154 # profile_dir requested early, force initialization
155 155 self.init_profile_dir()
156 156 return self.profile_dir
157 157
158 158 overwrite = Bool(False, config=True,
159 159 help="""Whether to overwrite existing config files when copying""")
160 160 auto_create = Bool(False, config=True,
161 161 help="""Whether to create profile dir if it doesn't exist""")
162 162
163 163 config_files = List(Unicode)
164 164 def _config_files_default(self):
165 165 return [self.config_file_name]
166 166
167 167 copy_config_files = Bool(False, config=True,
168 168 help="""Whether to install the default config files into the profile dir.
169 169 If a new profile is being created, and IPython contains config files for that
170 170 profile, then they will be staged into the new directory. Otherwise,
171 171 default config files will be automatically generated.
172 172 """)
173 173
174 174 verbose_crash = Bool(False, config=True,
175 175 help="""Create a massive crash report when IPython encounters what may be an
176 176 internal error. The default is to append a short message to the
177 177 usual traceback""")
178 178
179 179 # The class to use as the crash handler.
180 180 crash_handler_class = Type(crashhandler.CrashHandler)
181 181
182 182 @catch_config_error
183 183 def __init__(self, **kwargs):
184 184 super(BaseIPythonApplication, self).__init__(**kwargs)
185 185 # ensure current working directory exists
186 186 try:
187 187 directory = py3compat.getcwd()
188 188 except:
189 189 # exit if cwd doesn't exist
190 190 self.log.error("Current working directory doesn't exist.")
191 191 self.exit(1)
192 192
193 193 #-------------------------------------------------------------------------
194 194 # Various stages of Application creation
195 195 #-------------------------------------------------------------------------
196 196
197 197 def init_crash_handler(self):
198 198 """Create a crash handler, typically setting sys.excepthook to it."""
199 199 self.crash_handler = self.crash_handler_class(self)
200 200 sys.excepthook = self.excepthook
201 201 def unset_crashhandler():
202 202 sys.excepthook = sys.__excepthook__
203 203 atexit.register(unset_crashhandler)
204 204
205 205 def excepthook(self, etype, evalue, tb):
206 206 """this is sys.excepthook after init_crashhandler
207 207
208 208 set self.verbose_crash=True to use our full crashhandler, instead of
209 209 a regular traceback with a short message (crash_handler_lite)
210 210 """
211 211
212 212 if self.verbose_crash:
213 213 return self.crash_handler(etype, evalue, tb)
214 214 else:
215 215 return crashhandler.crash_handler_lite(etype, evalue, tb)
216 216
217 217 def _ipython_dir_changed(self, name, old, new):
218 218 if old is not None:
219 219 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
220 220 sys.getfilesystemencoding()
221 221 )
222 222 if str_old in sys.path:
223 223 sys.path.remove(str_old)
224 224 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
225 225 sys.getfilesystemencoding()
226 226 )
227 227 sys.path.append(str_path)
228 228 ensure_dir_exists(new)
229 229 readme = os.path.join(new, 'README')
230 230 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
231 231 if not os.path.exists(readme) and os.path.exists(readme_src):
232 232 shutil.copy(readme_src, readme)
233 233 for d in ('extensions', 'nbextensions'):
234 234 path = os.path.join(new, d)
235 235 try:
236 236 ensure_dir_exists(path)
237 237 except OSError:
238 238 # this will not be EEXIST
239 239 self.log.error("couldn't create path %s: %s", path, e)
240 240 self.log.debug("IPYTHONDIR set to: %s" % new)
241 241
242 242 def load_config_file(self, suppress_errors=True):
243 243 """Load the config file.
244 244
245 245 By default, errors in loading config are handled, and a warning
246 246 printed on screen. For testing, the suppress_errors option is set
247 247 to False, so errors will make tests fail.
248 248 """
249 249 self.log.debug("Searching path %s for config files", self.config_file_paths)
250 250 base_config = 'ipython_config.py'
251 251 self.log.debug("Attempting to load config file: %s" %
252 252 base_config)
253 253 try:
254 254 Application.load_config_file(
255 255 self,
256 256 base_config,
257 257 path=self.config_file_paths
258 258 )
259 259 except ConfigFileNotFound:
260 260 # ignore errors loading parent
261 261 self.log.debug("Config file %s not found", base_config)
262 262 pass
263 263
264 264 for config_file_name in self.config_files:
265 265 if not config_file_name or config_file_name == base_config:
266 266 continue
267 267 self.log.debug("Attempting to load config file: %s" %
268 268 self.config_file_name)
269 269 try:
270 270 Application.load_config_file(
271 271 self,
272 272 config_file_name,
273 273 path=self.config_file_paths
274 274 )
275 275 except ConfigFileNotFound:
276 276 # Only warn if the default config file was NOT being used.
277 277 if config_file_name in self.config_file_specified:
278 278 msg = self.log.warn
279 279 else:
280 280 msg = self.log.debug
281 281 msg("Config file not found, skipping: %s", config_file_name)
282 282 except:
283 283 # For testing purposes.
284 284 if not suppress_errors:
285 285 raise
286 286 self.log.warn("Error loading config file: %s" %
287 287 self.config_file_name, exc_info=True)
288 288
289 289 def init_profile_dir(self):
290 290 """initialize the profile dir"""
291 291 self._in_init_profile_dir = True
292 292 if self.profile_dir is not None:
293 293 # already ran
294 294 return
295 295 if 'ProfileDir.location' not in self.config:
296 296 # location not specified, find by profile name
297 297 try:
298 298 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
299 299 except ProfileDirError:
300 300 # not found, maybe create it (always create default profile)
301 301 if self.auto_create or self.profile == 'default':
302 302 try:
303 303 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
304 304 except ProfileDirError:
305 305 self.log.fatal("Could not create profile: %r"%self.profile)
306 306 self.exit(1)
307 307 else:
308 308 self.log.info("Created profile dir: %r"%p.location)
309 309 else:
310 310 self.log.fatal("Profile %r not found."%self.profile)
311 311 self.exit(1)
312 312 else:
313 313 self.log.debug("Using existing profile dir: %r"%p.location)
314 314 else:
315 315 location = self.config.ProfileDir.location
316 316 # location is fully specified
317 317 try:
318 318 p = ProfileDir.find_profile_dir(location, self.config)
319 319 except ProfileDirError:
320 320 # not found, maybe create it
321 321 if self.auto_create:
322 322 try:
323 323 p = ProfileDir.create_profile_dir(location, self.config)
324 324 except ProfileDirError:
325 325 self.log.fatal("Could not create profile directory: %r"%location)
326 326 self.exit(1)
327 327 else:
328 328 self.log.debug("Creating new profile dir: %r"%location)
329 329 else:
330 330 self.log.fatal("Profile directory %r not found."%location)
331 331 self.exit(1)
332 332 else:
333 333 self.log.info("Using existing profile dir: %r"%location)
334 334 # if profile_dir is specified explicitly, set profile name
335 335 dir_name = os.path.basename(p.location)
336 336 if dir_name.startswith('profile_'):
337 337 self.profile = dir_name[8:]
338 338
339 339 self.profile_dir = p
340 340 self.config_file_paths.append(p.location)
341 341 self._in_init_profile_dir = False
342 342
343 343 def init_config_files(self):
344 344 """[optionally] copy default config files into profile dir."""
345 345 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
346 346 # copy config files
347 347 path = self.builtin_profile_dir
348 348 if self.copy_config_files:
349 349 src = self.profile
350 350
351 351 cfg = self.config_file_name
352 352 if path and os.path.exists(os.path.join(path, cfg)):
353 353 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
354 354 cfg, src, self.profile_dir.location, self.overwrite)
355 355 )
356 356 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
357 357 else:
358 358 self.stage_default_config_file()
359 359 else:
360 360 # Still stage *bundled* config files, but not generated ones
361 361 # This is necessary for `ipython profile=sympy` to load the profile
362 362 # on the first go
363 363 files = glob.glob(os.path.join(path, '*.py'))
364 364 for fullpath in files:
365 365 cfg = os.path.basename(fullpath)
366 366 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
367 367 # file was copied
368 368 self.log.warn("Staging bundled %s from %s into %r"%(
369 369 cfg, self.profile, self.profile_dir.location)
370 370 )
371 371
372 372
373 373 def stage_default_config_file(self):
374 374 """auto generate default config file, and stage it into the profile."""
375 375 s = self.generate_config_file()
376 376 fname = os.path.join(self.profile_dir.location, self.config_file_name)
377 377 if self.overwrite or not os.path.exists(fname):
378 378 self.log.warn("Generating default config file: %r"%(fname))
379 379 with open(fname, 'w') as f:
380 380 f.write(s)
381 381
382 382 @catch_config_error
383 383 def initialize(self, argv=None):
384 384 # don't hook up crash handler before parsing command-line
385 385 self.parse_command_line(argv)
386 386 self.init_crash_handler()
387 387 if self.subapp is not None:
388 388 # stop here if subapp is taking over
389 389 return
390 390 cl_config = self.config
391 391 self.init_profile_dir()
392 392 self.init_config_files()
393 393 self.load_config_file()
394 394 # enforce cl-opts override configfile opts:
395 395 self.update_config(cl_config)
396 396
@@ -1,111 +1,112 b''
1 1 """
2 2 A context manager for managing things injected into :mod:`__builtin__`.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 * Fernando Perez
8 8 """
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2010-2011 The IPython Development Team.
11 11 #
12 12 # Distributed under the terms of the BSD License.
13 13 #
14 14 # Complete license in the file COPYING.txt, distributed with this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 from IPython.config.configurable import Configurable
22 22
23 23 from IPython.utils.py3compat import builtin_mod, iteritems
24 24 from IPython.utils.traitlets import Instance
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes and functions
28 28 #-----------------------------------------------------------------------------
29 29
30 30 class __BuiltinUndefined(object): pass
31 31 BuiltinUndefined = __BuiltinUndefined()
32 32
33 33 class __HideBuiltin(object): pass
34 34 HideBuiltin = __HideBuiltin()
35 35
36 36
37 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 42 def __init__(self, shell=None):
42 43 super(BuiltinTrap, self).__init__(shell=shell, config=None)
43 44 self._orig_builtins = {}
44 45 # We define this to track if a single BuiltinTrap is nested.
45 46 # Only turn off the trap when the outermost call to __exit__ is made.
46 47 self._nested_level = 0
47 48 self.shell = shell
48 49 # builtins we always add - if set to HideBuiltin, they will just
49 50 # be removed instead of being replaced by something else
50 51 self.auto_builtins = {'exit': HideBuiltin,
51 52 'quit': HideBuiltin,
52 53 'get_ipython': self.shell.get_ipython,
53 54 }
54 55 # Recursive reload function
55 56 try:
56 57 from IPython.lib import deepreload
57 58 if self.shell.deep_reload:
58 59 self.auto_builtins['reload'] = deepreload.reload
59 60 else:
60 61 self.auto_builtins['dreload']= deepreload.reload
61 62 except ImportError:
62 63 pass
63 64
64 65 def __enter__(self):
65 66 if self._nested_level == 0:
66 67 self.activate()
67 68 self._nested_level += 1
68 69 # I return self, so callers can use add_builtin in a with clause.
69 70 return self
70 71
71 72 def __exit__(self, type, value, traceback):
72 73 if self._nested_level == 1:
73 74 self.deactivate()
74 75 self._nested_level -= 1
75 76 # Returning False will cause exceptions to propagate
76 77 return False
77 78
78 79 def add_builtin(self, key, value):
79 80 """Add a builtin and save the original."""
80 81 bdict = builtin_mod.__dict__
81 82 orig = bdict.get(key, BuiltinUndefined)
82 83 if value is HideBuiltin:
83 84 if orig is not BuiltinUndefined: #same as 'key in bdict'
84 85 self._orig_builtins[key] = orig
85 86 del bdict[key]
86 87 else:
87 88 self._orig_builtins[key] = orig
88 89 bdict[key] = value
89 90
90 91 def remove_builtin(self, key, orig):
91 92 """Remove an added builtin and re-set the original."""
92 93 if orig is BuiltinUndefined:
93 94 del builtin_mod.__dict__[key]
94 95 else:
95 96 builtin_mod.__dict__[key] = orig
96 97
97 98 def activate(self):
98 99 """Store ipython references in the __builtin__ namespace."""
99 100
100 101 add_builtin = self.add_builtin
101 102 for name, func in iteritems(self.auto_builtins):
102 103 add_builtin(name, func)
103 104
104 105 def deactivate(self):
105 106 """Remove any builtins which might have been added by add_builtins, or
106 107 restore overwritten ones to their previous values."""
107 108 remove_builtin = self.remove_builtin
108 109 for key, val in iteritems(self._orig_builtins):
109 110 remove_builtin(key, val)
110 111 self._orig_builtins.clear()
111 112 self._builtins_added = False
@@ -1,282 +1,283 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Displayhook for IPython.
3 3
4 4 This defines a callable class that IPython uses for `sys.displayhook`.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 from __future__ import print_function
11 11
12 12 import sys
13 13
14 14 from IPython.core.formatters import _safe_get_formatter_method
15 15 from IPython.config.configurable import Configurable
16 16 from IPython.utils import io
17 17 from IPython.utils.py3compat import builtin_mod
18 18 from IPython.utils.traitlets import Instance, Float
19 19 from IPython.utils.warn import warn
20 20
21 21 # TODO: Move the various attributes (cache_size, [others now moved]). Some
22 22 # of these are also attributes of InteractiveShell. They should be on ONE object
23 23 # only and the other objects should ask that one object for their values.
24 24
25 25 class DisplayHook(Configurable):
26 26 """The custom IPython displayhook to replace sys.displayhook.
27 27
28 28 This class does many things, but the basic idea is that it is a callable
29 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 34 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
34 35 allow_none=True)
35 36 cull_fraction = Float(0.2)
36 37
37 38 def __init__(self, shell=None, cache_size=1000, **kwargs):
38 39 super(DisplayHook, self).__init__(shell=shell, **kwargs)
39 40 cache_size_min = 3
40 41 if cache_size <= 0:
41 42 self.do_full_cache = 0
42 43 cache_size = 0
43 44 elif cache_size < cache_size_min:
44 45 self.do_full_cache = 0
45 46 cache_size = 0
46 47 warn('caching was disabled (min value for cache size is %s).' %
47 48 cache_size_min,level=3)
48 49 else:
49 50 self.do_full_cache = 1
50 51
51 52 self.cache_size = cache_size
52 53
53 54 # we need a reference to the user-level namespace
54 55 self.shell = shell
55 56
56 57 self._,self.__,self.___ = '','',''
57 58
58 59 # these are deliberately global:
59 60 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
60 61 self.shell.user_ns.update(to_user_ns)
61 62
62 63 @property
63 64 def prompt_count(self):
64 65 return self.shell.execution_count
65 66
66 67 #-------------------------------------------------------------------------
67 68 # Methods used in __call__. Override these methods to modify the behavior
68 69 # of the displayhook.
69 70 #-------------------------------------------------------------------------
70 71
71 72 def check_for_underscore(self):
72 73 """Check if the user has set the '_' variable by hand."""
73 74 # If something injected a '_' variable in __builtin__, delete
74 75 # ipython's automatic one so we don't clobber that. gettext() in
75 76 # particular uses _, so we need to stay away from it.
76 77 if '_' in builtin_mod.__dict__:
77 78 try:
78 79 del self.shell.user_ns['_']
79 80 except KeyError:
80 81 pass
81 82
82 83 def quiet(self):
83 84 """Should we silence the display hook because of ';'?"""
84 85 # do not print output if input ends in ';'
85 86 try:
86 87 cell = self.shell.history_manager.input_hist_parsed[self.prompt_count]
87 88 return cell.rstrip().endswith(';')
88 89 except IndexError:
89 90 # some uses of ipshellembed may fail here
90 91 return False
91 92
92 93 def start_displayhook(self):
93 94 """Start the displayhook, initializing resources."""
94 95 pass
95 96
96 97 def write_output_prompt(self):
97 98 """Write the output prompt.
98 99
99 100 The default implementation simply writes the prompt to
100 101 ``io.stdout``.
101 102 """
102 103 # Use write, not print which adds an extra space.
103 104 io.stdout.write(self.shell.separate_out)
104 105 outprompt = self.shell.prompt_manager.render('out')
105 106 if self.do_full_cache:
106 107 io.stdout.write(outprompt)
107 108
108 109 def compute_format_data(self, result):
109 110 """Compute format data of the object to be displayed.
110 111
111 112 The format data is a generalization of the :func:`repr` of an object.
112 113 In the default implementation the format data is a :class:`dict` of
113 114 key value pair where the keys are valid MIME types and the values
114 115 are JSON'able data structure containing the raw data for that MIME
115 116 type. It is up to frontends to determine pick a MIME to to use and
116 117 display that data in an appropriate manner.
117 118
118 119 This method only computes the format data for the object and should
119 120 NOT actually print or write that to a stream.
120 121
121 122 Parameters
122 123 ----------
123 124 result : object
124 125 The Python object passed to the display hook, whose format will be
125 126 computed.
126 127
127 128 Returns
128 129 -------
129 130 (format_dict, md_dict) : dict
130 131 format_dict is a :class:`dict` whose keys are valid MIME types and values are
131 132 JSON'able raw data for that MIME type. It is recommended that
132 133 all return values of this should always include the "text/plain"
133 134 MIME type representation of the object.
134 135 md_dict is a :class:`dict` with the same MIME type keys
135 136 of metadata associated with each output.
136 137
137 138 """
138 139 return self.shell.display_formatter.format(result)
139 140
140 141 def write_format_data(self, format_dict, md_dict=None):
141 142 """Write the format data dict to the frontend.
142 143
143 144 This default version of this method simply writes the plain text
144 145 representation of the object to ``io.stdout``. Subclasses should
145 146 override this method to send the entire `format_dict` to the
146 147 frontends.
147 148
148 149 Parameters
149 150 ----------
150 151 format_dict : dict
151 152 The format dict for the object passed to `sys.displayhook`.
152 153 md_dict : dict (optional)
153 154 The metadata dict to be associated with the display data.
154 155 """
155 156 if 'text/plain' not in format_dict:
156 157 # nothing to do
157 158 return
158 159 # We want to print because we want to always make sure we have a
159 160 # newline, even if all the prompt separators are ''. This is the
160 161 # standard IPython behavior.
161 162 result_repr = format_dict['text/plain']
162 163 if '\n' in result_repr:
163 164 # So that multi-line strings line up with the left column of
164 165 # the screen, instead of having the output prompt mess up
165 166 # their first line.
166 167 # We use the prompt template instead of the expanded prompt
167 168 # because the expansion may add ANSI escapes that will interfere
168 169 # with our ability to determine whether or not we should add
169 170 # a newline.
170 171 prompt_template = self.shell.prompt_manager.out_template
171 172 if prompt_template and not prompt_template.endswith('\n'):
172 173 # But avoid extraneous empty lines.
173 174 result_repr = '\n' + result_repr
174 175
175 176 print(result_repr, file=io.stdout)
176 177
177 178 def update_user_ns(self, result):
178 179 """Update user_ns with various things like _, __, _1, etc."""
179 180
180 181 # Avoid recursive reference when displaying _oh/Out
181 182 if result is not self.shell.user_ns['_oh']:
182 183 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
183 184 self.cull_cache()
184 185 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
185 186 # we cause buggy behavior for things like gettext).
186 187
187 188 if '_' not in builtin_mod.__dict__:
188 189 self.___ = self.__
189 190 self.__ = self._
190 191 self._ = result
191 192 self.shell.push({'_':self._,
192 193 '__':self.__,
193 194 '___':self.___}, interactive=False)
194 195
195 196 # hackish access to top-level namespace to create _1,_2... dynamically
196 197 to_main = {}
197 198 if self.do_full_cache:
198 199 new_result = '_'+repr(self.prompt_count)
199 200 to_main[new_result] = result
200 201 self.shell.push(to_main, interactive=False)
201 202 self.shell.user_ns['_oh'][self.prompt_count] = result
202 203
203 204 def fill_exec_result(self, result):
204 205 if self.exec_result is not None:
205 206 self.exec_result.result = result
206 207
207 208 def log_output(self, format_dict):
208 209 """Log the output."""
209 210 if 'text/plain' not in format_dict:
210 211 # nothing to do
211 212 return
212 213 if self.shell.logger.log_output:
213 214 self.shell.logger.log_write(format_dict['text/plain'], 'output')
214 215 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
215 216 format_dict['text/plain']
216 217
217 218 def finish_displayhook(self):
218 219 """Finish up all displayhook activities."""
219 220 io.stdout.write(self.shell.separate_out2)
220 221 io.stdout.flush()
221 222
222 223 def __call__(self, result=None):
223 224 """Printing with history cache management.
224 225
225 226 This is invoked everytime the interpreter needs to print, and is
226 227 activated by setting the variable sys.displayhook to it.
227 228 """
228 229 self.check_for_underscore()
229 230 if result is not None and not self.quiet():
230 231 self.start_displayhook()
231 232 self.write_output_prompt()
232 233 format_dict, md_dict = self.compute_format_data(result)
233 234 self.update_user_ns(result)
234 235 self.fill_exec_result(result)
235 236 if format_dict:
236 237 self.write_format_data(format_dict, md_dict)
237 238 self.log_output(format_dict)
238 239 self.finish_displayhook()
239 240
240 241 def cull_cache(self):
241 242 """Output cache is full, cull the oldest entries"""
242 243 oh = self.shell.user_ns.get('_oh', {})
243 244 sz = len(oh)
244 245 cull_count = max(int(sz * self.cull_fraction), 2)
245 246 warn('Output cache limit (currently {sz} entries) hit.\n'
246 247 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
247 248
248 249 for i, n in enumerate(sorted(oh)):
249 250 if i >= cull_count:
250 251 break
251 252 self.shell.user_ns.pop('_%i' % n, None)
252 253 oh.pop(n, None)
253 254
254 255
255 256 def flush(self):
256 257 if not self.do_full_cache:
257 258 raise ValueError("You shouldn't have reached the cache flush "
258 259 "if full caching is not enabled!")
259 260 # delete auto-generated vars from global namespace
260 261
261 262 for n in range(1,self.prompt_count + 1):
262 263 key = '_'+repr(n)
263 264 try:
264 265 del self.shell.user_ns[key]
265 266 except: pass
266 267 # In some embedded circumstances, the user_ns doesn't have the
267 268 # '_oh' key set up.
268 269 oh = self.shell.user_ns.get('_oh', None)
269 270 if oh is not None:
270 271 oh.clear()
271 272
272 273 # Release our own references to objects:
273 274 self._, self.__, self.___ = '', '', ''
274 275
275 276 if '_' not in builtin_mod.__dict__:
276 277 self.shell.user_ns.update({'_':None,'__':None, '___':None})
277 278 import gc
278 279 # TODO: Is this really needed?
279 280 # IronPython blocks here forever
280 281 if sys.platform != "cli":
281 282 gc.collect()
282 283
@@ -1,175 +1,176 b''
1 1 # encoding: utf-8
2 2 """A class for managing IPython extensions."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 from shutil import copyfile
9 9 import sys
10 10
11 11 from IPython.config.configurable import Configurable
12 12 from IPython.utils.path import ensure_dir_exists
13 13 from IPython.utils.traitlets import Instance
14 14 from IPython.utils.py3compat import PY3
15 15 if PY3:
16 16 from imp import reload
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Main class
20 20 #-----------------------------------------------------------------------------
21 21
22 22 class ExtensionManager(Configurable):
23 23 """A class to manage IPython extensions.
24 24
25 25 An IPython extension is an importable Python module that has
26 26 a function with the signature::
27 27
28 28 def load_ipython_extension(ipython):
29 29 # Do things with ipython
30 30
31 31 This function is called after your extension is imported and the
32 32 currently active :class:`InteractiveShell` instance is passed as
33 33 the only argument. You can do anything you want with IPython at
34 34 that point, including defining new magic and aliases, adding new
35 35 components, etc.
36 36
37 37 You can also optionally define an :func:`unload_ipython_extension(ipython)`
38 38 function, which will be called if the user unloads or reloads the extension.
39 39 The extension manager will only call :func:`load_ipython_extension` again
40 40 if the extension is reloaded.
41 41
42 42 You can put your extension modules anywhere you want, as long as
43 43 they can be imported by Python's standard import mechanism. However,
44 44 to make it easy to write extensions, you can also put your extensions
45 45 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
46 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 52 def __init__(self, shell=None, **kwargs):
52 53 super(ExtensionManager, self).__init__(shell=shell, **kwargs)
53 54 self.shell.on_trait_change(
54 55 self._on_ipython_dir_changed, 'ipython_dir'
55 56 )
56 57 self.loaded = set()
57 58
58 59 def __del__(self):
59 60 self.shell.on_trait_change(
60 61 self._on_ipython_dir_changed, 'ipython_dir', remove=True
61 62 )
62 63
63 64 @property
64 65 def ipython_extension_dir(self):
65 66 return os.path.join(self.shell.ipython_dir, u'extensions')
66 67
67 68 def _on_ipython_dir_changed(self):
68 69 ensure_dir_exists(self.ipython_extension_dir)
69 70
70 71 def load_extension(self, module_str):
71 72 """Load an IPython extension by its module name.
72 73
73 74 Returns the string "already loaded" if the extension is already loaded,
74 75 "no load function" if the module doesn't have a load_ipython_extension
75 76 function, or None if it succeeded.
76 77 """
77 78 if module_str in self.loaded:
78 79 return "already loaded"
79 80
80 81 from IPython.utils.syspathcontext import prepended_to_syspath
81 82
82 83 with self.shell.builtin_trap:
83 84 if module_str not in sys.modules:
84 85 with prepended_to_syspath(self.ipython_extension_dir):
85 86 __import__(module_str)
86 87 mod = sys.modules[module_str]
87 88 if self._call_load_ipython_extension(mod):
88 89 self.loaded.add(module_str)
89 90 else:
90 91 return "no load function"
91 92
92 93 def unload_extension(self, module_str):
93 94 """Unload an IPython extension by its module name.
94 95
95 96 This function looks up the extension's name in ``sys.modules`` and
96 97 simply calls ``mod.unload_ipython_extension(self)``.
97 98
98 99 Returns the string "no unload function" if the extension doesn't define
99 100 a function to unload itself, "not loaded" if the extension isn't loaded,
100 101 otherwise None.
101 102 """
102 103 if module_str not in self.loaded:
103 104 return "not loaded"
104 105
105 106 if module_str in sys.modules:
106 107 mod = sys.modules[module_str]
107 108 if self._call_unload_ipython_extension(mod):
108 109 self.loaded.discard(module_str)
109 110 else:
110 111 return "no unload function"
111 112
112 113 def reload_extension(self, module_str):
113 114 """Reload an IPython extension by calling reload.
114 115
115 116 If the module has not been loaded before,
116 117 :meth:`InteractiveShell.load_extension` is called. Otherwise
117 118 :func:`reload` is called and then the :func:`load_ipython_extension`
118 119 function of the module, if it exists is called.
119 120 """
120 121 from IPython.utils.syspathcontext import prepended_to_syspath
121 122
122 123 if (module_str in self.loaded) and (module_str in sys.modules):
123 124 self.unload_extension(module_str)
124 125 mod = sys.modules[module_str]
125 126 with prepended_to_syspath(self.ipython_extension_dir):
126 127 reload(mod)
127 128 if self._call_load_ipython_extension(mod):
128 129 self.loaded.add(module_str)
129 130 else:
130 131 self.load_extension(module_str)
131 132
132 133 def _call_load_ipython_extension(self, mod):
133 134 if hasattr(mod, 'load_ipython_extension'):
134 135 mod.load_ipython_extension(self.shell)
135 136 return True
136 137
137 138 def _call_unload_ipython_extension(self, mod):
138 139 if hasattr(mod, 'unload_ipython_extension'):
139 140 mod.unload_ipython_extension(self.shell)
140 141 return True
141 142
142 143 def install_extension(self, url, filename=None):
143 144 """Download and install an IPython extension.
144 145
145 146 If filename is given, the file will be so named (inside the extension
146 147 directory). Otherwise, the name from the URL will be used. The file must
147 148 have a .py or .zip extension; otherwise, a ValueError will be raised.
148 149
149 150 Returns the full path to the installed file.
150 151 """
151 152 # Ensure the extension directory exists
152 153 ensure_dir_exists(self.ipython_extension_dir)
153 154
154 155 if os.path.isfile(url):
155 156 src_filename = os.path.basename(url)
156 157 copy = copyfile
157 158 else:
158 159 # Deferred imports
159 160 try:
160 161 from urllib.parse import urlparse # Py3
161 162 from urllib.request import urlretrieve
162 163 except ImportError:
163 164 from urlparse import urlparse
164 165 from urllib import urlretrieve
165 166 src_filename = urlparse(url).path.split('/')[-1]
166 167 copy = urlretrieve
167 168
168 169 if filename is None:
169 170 filename = src_filename
170 171 if os.path.splitext(filename)[1] not in ('.py', '.zip'):
171 172 raise ValueError("The file must have a .py or .zip extension", filename)
172 173
173 174 filename = os.path.join(self.ipython_extension_dir, filename)
174 175 copy(url, filename)
175 176 return filename
@@ -1,870 +1,872 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010-2011 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 try:
21 21 import sqlite3
22 22 except ImportError:
23 23 try:
24 24 from pysqlite2 import dbapi2 as sqlite3
25 25 except ImportError:
26 26 sqlite3 = None
27 27 import threading
28 28
29 29 # Our own packages
30 30 from IPython.config.configurable import Configurable
31 31 from decorator import decorator
32 32 from IPython.utils.decorators import undoc
33 33 from IPython.utils.path import locate_profile
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.traitlets import (
36 36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
37 37 )
38 38 from IPython.utils.warn import warn
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Classes and functions
42 42 #-----------------------------------------------------------------------------
43 43
44 44 @undoc
45 45 class DummyDB(object):
46 46 """Dummy DB that will act as a black hole for history.
47 47
48 48 Only used in the absence of sqlite"""
49 49 def execute(*args, **kwargs):
50 50 return []
51 51
52 52 def commit(self, *args, **kwargs):
53 53 pass
54 54
55 55 def __enter__(self, *args, **kwargs):
56 56 pass
57 57
58 58 def __exit__(self, *args, **kwargs):
59 59 pass
60 60
61 61
62 62 @decorator
63 63 def needs_sqlite(f, self, *a, **kw):
64 64 """Decorator: return an empty list in the absence of sqlite."""
65 65 if sqlite3 is None or not self.enabled:
66 66 return []
67 67 else:
68 68 return f(self, *a, **kw)
69 69
70 70
71 71 if sqlite3 is not None:
72 72 DatabaseError = sqlite3.DatabaseError
73 73 else:
74 74 @undoc
75 75 class DatabaseError(Exception):
76 76 "Dummy exception when sqlite could not be imported. Should never occur."
77 77
78 78 @decorator
79 79 def catch_corrupt_db(f, self, *a, **kw):
80 80 """A decorator which wraps HistoryAccessor method calls to catch errors from
81 81 a corrupt SQLite database, move the old database out of the way, and create
82 82 a new one.
83 83 """
84 84 try:
85 85 return f(self, *a, **kw)
86 86 except DatabaseError:
87 87 if os.path.isfile(self.hist_file):
88 88 # Try to move the file out of the way
89 89 base,ext = os.path.splitext(self.hist_file)
90 90 newpath = base + '-corrupt' + ext
91 91 os.rename(self.hist_file, newpath)
92 92 self.init_db()
93 93 print("ERROR! History file wasn't a valid SQLite database.",
94 94 "It was moved to %s" % newpath, "and a new file created.")
95 95 return []
96 96
97 97 else:
98 98 # The hist_file is probably :memory: or something else.
99 99 raise
100 100
101 101 class HistoryAccessorBase(Configurable):
102 102 """An abstract class for History Accessors """
103 103
104 104 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
105 105 raise NotImplementedError
106 106
107 107 def search(self, pattern="*", raw=True, search_raw=True,
108 108 output=False, n=None, unique=False):
109 109 raise NotImplementedError
110 110
111 111 def get_range(self, session, start=1, stop=None, raw=True,output=False):
112 112 raise NotImplementedError
113 113
114 114 def get_range_by_str(self, rangestr, raw=True, output=False):
115 115 raise NotImplementedError
116 116
117 117
118 118 class HistoryAccessor(HistoryAccessorBase):
119 119 """Access the history database without adding to it.
120 120
121 121 This is intended for use by standalone history tools. IPython shells use
122 122 HistoryManager, below, which is a subclass of this."""
123 123
124 124 # String holding the path to the history file
125 125 hist_file = Unicode(config=True,
126 126 help="""Path to file to use for SQLite history database.
127 127
128 128 By default, IPython will put the history database in the IPython
129 129 profile directory. If you would rather share one history among
130 130 profiles, you can set this value in each, so that they are consistent.
131 131
132 132 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
133 133 mounts. If you see IPython hanging, try setting this to something on a
134 134 local disk, e.g::
135 135
136 136 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
137 137
138 138 """)
139 139
140 140 enabled = Bool(True, config=True,
141 141 help="""enable the SQLite history
142 142
143 143 set enabled=False to disable the SQLite history,
144 144 in which case there will be no stored history, no SQLite connection,
145 145 and no background saving thread. This may be necessary in some
146 146 threaded environments where IPython is embedded.
147 147 """
148 148 )
149 149
150 150 connection_options = Dict(config=True,
151 151 help="""Options for configuring the SQLite connection
152 152
153 153 These options are passed as keyword args to sqlite3.connect
154 154 when establishing database conenctions.
155 155 """
156 156 )
157 157
158 158 # The SQLite database
159 159 db = Any()
160 160 def _db_changed(self, name, old, new):
161 161 """validate the db, since it can be an Instance of two different types"""
162 162 connection_types = (DummyDB,)
163 163 if sqlite3 is not None:
164 164 connection_types = (DummyDB, sqlite3.Connection)
165 165 if not isinstance(new, connection_types):
166 166 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
167 167 (self.__class__.__name__, new)
168 168 raise TraitError(msg)
169 169
170 170 def __init__(self, profile='default', hist_file=u'', **traits):
171 171 """Create a new history accessor.
172 172
173 173 Parameters
174 174 ----------
175 175 profile : str
176 176 The name of the profile from which to open history.
177 177 hist_file : str
178 178 Path to an SQLite history database stored by IPython. If specified,
179 179 hist_file overrides profile.
180 180 config : :class:`~IPython.config.loader.Config`
181 181 Config object. hist_file can also be set through this.
182 182 """
183 183 # We need a pointer back to the shell for various tasks.
184 184 super(HistoryAccessor, self).__init__(**traits)
185 185 # defer setting hist_file from kwarg until after init,
186 186 # otherwise the default kwarg value would clobber any value
187 187 # set by config
188 188 if hist_file:
189 189 self.hist_file = hist_file
190 190
191 191 if self.hist_file == u'':
192 192 # No one has set the hist_file, yet.
193 193 self.hist_file = self._get_hist_file_name(profile)
194 194
195 195 if sqlite3 is None and self.enabled:
196 196 warn("IPython History requires SQLite, your history will not be saved")
197 197 self.enabled = False
198 198
199 199 self.init_db()
200 200
201 201 def _get_hist_file_name(self, profile='default'):
202 202 """Find the history file for the given profile name.
203 203
204 204 This is overridden by the HistoryManager subclass, to use the shell's
205 205 active profile.
206 206
207 207 Parameters
208 208 ----------
209 209 profile : str
210 210 The name of a profile which has a history file.
211 211 """
212 212 return os.path.join(locate_profile(profile), 'history.sqlite')
213 213
214 214 @catch_corrupt_db
215 215 def init_db(self):
216 216 """Connect to the database, and create tables if necessary."""
217 217 if not self.enabled:
218 218 self.db = DummyDB()
219 219 return
220 220
221 221 # use detect_types so that timestamps return datetime objects
222 222 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
223 223 kwargs.update(self.connection_options)
224 224 self.db = sqlite3.connect(self.hist_file, **kwargs)
225 225 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
226 226 primary key autoincrement, start timestamp,
227 227 end timestamp, num_cmds integer, remark text)""")
228 228 self.db.execute("""CREATE TABLE IF NOT EXISTS history
229 229 (session integer, line integer, source text, source_raw text,
230 230 PRIMARY KEY (session, line))""")
231 231 # Output history is optional, but ensure the table's there so it can be
232 232 # enabled later.
233 233 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
234 234 (session integer, line integer, output text,
235 235 PRIMARY KEY (session, line))""")
236 236 self.db.commit()
237 237
238 238 def writeout_cache(self):
239 239 """Overridden by HistoryManager to dump the cache before certain
240 240 database lookups."""
241 241 pass
242 242
243 243 ## -------------------------------
244 244 ## Methods for retrieving history:
245 245 ## -------------------------------
246 246 def _run_sql(self, sql, params, raw=True, output=False):
247 247 """Prepares and runs an SQL query for the history database.
248 248
249 249 Parameters
250 250 ----------
251 251 sql : str
252 252 Any filtering expressions to go after SELECT ... FROM ...
253 253 params : tuple
254 254 Parameters passed to the SQL query (to replace "?")
255 255 raw, output : bool
256 256 See :meth:`get_range`
257 257
258 258 Returns
259 259 -------
260 260 Tuples as :meth:`get_range`
261 261 """
262 262 toget = 'source_raw' if raw else 'source'
263 263 sqlfrom = "history"
264 264 if output:
265 265 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
266 266 toget = "history.%s, output_history.output" % toget
267 267 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
268 268 (toget, sqlfrom) + sql, params)
269 269 if output: # Regroup into 3-tuples, and parse JSON
270 270 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
271 271 return cur
272 272
273 273 @needs_sqlite
274 274 @catch_corrupt_db
275 275 def get_session_info(self, session):
276 276 """Get info about a session.
277 277
278 278 Parameters
279 279 ----------
280 280
281 281 session : int
282 282 Session number to retrieve.
283 283
284 284 Returns
285 285 -------
286 286
287 287 session_id : int
288 288 Session ID number
289 289 start : datetime
290 290 Timestamp for the start of the session.
291 291 end : datetime
292 292 Timestamp for the end of the session, or None if IPython crashed.
293 293 num_cmds : int
294 294 Number of commands run, or None if IPython crashed.
295 295 remark : unicode
296 296 A manually set description.
297 297 """
298 298 query = "SELECT * from sessions where session == ?"
299 299 return self.db.execute(query, (session,)).fetchone()
300 300
301 301 @catch_corrupt_db
302 302 def get_last_session_id(self):
303 303 """Get the last session ID currently in the database.
304 304
305 305 Within IPython, this should be the same as the value stored in
306 306 :attr:`HistoryManager.session_number`.
307 307 """
308 308 for record in self.get_tail(n=1, include_latest=True):
309 309 return record[0]
310 310
311 311 @catch_corrupt_db
312 312 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
313 313 """Get the last n lines from the history database.
314 314
315 315 Parameters
316 316 ----------
317 317 n : int
318 318 The number of lines to get
319 319 raw, output : bool
320 320 See :meth:`get_range`
321 321 include_latest : bool
322 322 If False (default), n+1 lines are fetched, and the latest one
323 323 is discarded. This is intended to be used where the function
324 324 is called by a user command, which it should not return.
325 325
326 326 Returns
327 327 -------
328 328 Tuples as :meth:`get_range`
329 329 """
330 330 self.writeout_cache()
331 331 if not include_latest:
332 332 n += 1
333 333 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
334 334 (n,), raw=raw, output=output)
335 335 if not include_latest:
336 336 return reversed(list(cur)[1:])
337 337 return reversed(list(cur))
338 338
339 339 @catch_corrupt_db
340 340 def search(self, pattern="*", raw=True, search_raw=True,
341 341 output=False, n=None, unique=False):
342 342 """Search the database using unix glob-style matching (wildcards
343 343 * and ?).
344 344
345 345 Parameters
346 346 ----------
347 347 pattern : str
348 348 The wildcarded pattern to match when searching
349 349 search_raw : bool
350 350 If True, search the raw input, otherwise, the parsed input
351 351 raw, output : bool
352 352 See :meth:`get_range`
353 353 n : None or int
354 354 If an integer is given, it defines the limit of
355 355 returned entries.
356 356 unique : bool
357 357 When it is true, return only unique entries.
358 358
359 359 Returns
360 360 -------
361 361 Tuples as :meth:`get_range`
362 362 """
363 363 tosearch = "source_raw" if search_raw else "source"
364 364 if output:
365 365 tosearch = "history." + tosearch
366 366 self.writeout_cache()
367 367 sqlform = "WHERE %s GLOB ?" % tosearch
368 368 params = (pattern,)
369 369 if unique:
370 370 sqlform += ' GROUP BY {0}'.format(tosearch)
371 371 if n is not None:
372 372 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
373 373 params += (n,)
374 374 elif unique:
375 375 sqlform += " ORDER BY session, line"
376 376 cur = self._run_sql(sqlform, params, raw=raw, output=output)
377 377 if n is not None:
378 378 return reversed(list(cur))
379 379 return cur
380 380
381 381 @catch_corrupt_db
382 382 def get_range(self, session, start=1, stop=None, raw=True,output=False):
383 383 """Retrieve input by session.
384 384
385 385 Parameters
386 386 ----------
387 387 session : int
388 388 Session number to retrieve.
389 389 start : int
390 390 First line to retrieve.
391 391 stop : int
392 392 End of line range (excluded from output itself). If None, retrieve
393 393 to the end of the session.
394 394 raw : bool
395 395 If True, return untranslated input
396 396 output : bool
397 397 If True, attempt to include output. This will be 'real' Python
398 398 objects for the current session, or text reprs from previous
399 399 sessions if db_log_output was enabled at the time. Where no output
400 400 is found, None is used.
401 401
402 402 Returns
403 403 -------
404 404 entries
405 405 An iterator over the desired lines. Each line is a 3-tuple, either
406 406 (session, line, input) if output is False, or
407 407 (session, line, (input, output)) if output is True.
408 408 """
409 409 if stop:
410 410 lineclause = "line >= ? AND line < ?"
411 411 params = (session, start, stop)
412 412 else:
413 413 lineclause = "line>=?"
414 414 params = (session, start)
415 415
416 416 return self._run_sql("WHERE session==? AND %s" % lineclause,
417 417 params, raw=raw, output=output)
418 418
419 419 def get_range_by_str(self, rangestr, raw=True, output=False):
420 420 """Get lines of history from a string of ranges, as used by magic
421 421 commands %hist, %save, %macro, etc.
422 422
423 423 Parameters
424 424 ----------
425 425 rangestr : str
426 426 A string specifying ranges, e.g. "5 ~2/1-4". See
427 427 :func:`magic_history` for full details.
428 428 raw, output : bool
429 429 As :meth:`get_range`
430 430
431 431 Returns
432 432 -------
433 433 Tuples as :meth:`get_range`
434 434 """
435 435 for sess, s, e in extract_hist_ranges(rangestr):
436 436 for line in self.get_range(sess, s, e, raw=raw, output=output):
437 437 yield line
438 438
439 439
440 440 class HistoryManager(HistoryAccessor):
441 441 """A class to organize all history-related functionality in one place.
442 442 """
443 443 # Public interface
444 444
445 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 448 # Lists to hold processed and raw history. These start with a blank entry
448 449 # so that we can index them starting from 1
449 450 input_hist_parsed = List([""])
450 451 input_hist_raw = List([""])
451 452 # A list of directories visited during session
452 453 dir_hist = List()
453 454 def _dir_hist_default(self):
454 455 try:
455 456 return [py3compat.getcwd()]
456 457 except OSError:
457 458 return []
458 459
459 460 # A dict of output history, keyed with ints from the shell's
460 461 # execution count.
461 462 output_hist = Dict()
462 463 # The text/plain repr of outputs.
463 464 output_hist_reprs = Dict()
464 465
465 466 # The number of the current session in the history database
466 467 session_number = Integer()
467 468
468 469 db_log_output = Bool(False, config=True,
469 470 help="Should the history database include output? (default: no)"
470 471 )
471 472 db_cache_size = Integer(0, config=True,
472 473 help="Write to database every x commands (higher values save disk access & power).\n"
473 474 "Values of 1 or less effectively disable caching."
474 475 )
475 476 # The input and output caches
476 477 db_input_cache = List()
477 478 db_output_cache = List()
478 479
479 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 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 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 488 # Private interface
487 489 # Variables used to store the three last inputs from the user. On each new
488 490 # history update, we populate the user's namespace with these, shifted as
489 491 # necessary.
490 492 _i00 = Unicode(u'')
491 493 _i = Unicode(u'')
492 494 _ii = Unicode(u'')
493 495 _iii = Unicode(u'')
494 496
495 497 # A regex matching all forms of the exit command, so that we don't store
496 498 # them in the history (it's annoying to rewind the first entry and land on
497 499 # an exit call).
498 500 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
499 501
500 502 def __init__(self, shell=None, config=None, **traits):
501 503 """Create a new history manager associated with a shell instance.
502 504 """
503 505 # We need a pointer back to the shell for various tasks.
504 506 super(HistoryManager, self).__init__(shell=shell, config=config,
505 507 **traits)
506 508 self.save_flag = threading.Event()
507 509 self.db_input_cache_lock = threading.Lock()
508 510 self.db_output_cache_lock = threading.Lock()
509 511 if self.enabled and self.hist_file != ':memory:':
510 512 self.save_thread = HistorySavingThread(self)
511 513 self.save_thread.start()
512 514
513 515 self.new_session()
514 516
515 517 def _get_hist_file_name(self, profile=None):
516 518 """Get default history file name based on the Shell's profile.
517 519
518 520 The profile parameter is ignored, but must exist for compatibility with
519 521 the parent class."""
520 522 profile_dir = self.shell.profile_dir.location
521 523 return os.path.join(profile_dir, 'history.sqlite')
522 524
523 525 @needs_sqlite
524 526 def new_session(self, conn=None):
525 527 """Get a new session number."""
526 528 if conn is None:
527 529 conn = self.db
528 530
529 531 with conn:
530 532 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
531 533 NULL, "") """, (datetime.datetime.now(),))
532 534 self.session_number = cur.lastrowid
533 535
534 536 def end_session(self):
535 537 """Close the database session, filling in the end time and line count."""
536 538 self.writeout_cache()
537 539 with self.db:
538 540 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
539 541 session==?""", (datetime.datetime.now(),
540 542 len(self.input_hist_parsed)-1, self.session_number))
541 543 self.session_number = 0
542 544
543 545 def name_session(self, name):
544 546 """Give the current session a name in the history database."""
545 547 with self.db:
546 548 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
547 549 (name, self.session_number))
548 550
549 551 def reset(self, new_session=True):
550 552 """Clear the session history, releasing all object references, and
551 553 optionally open a new session."""
552 554 self.output_hist.clear()
553 555 # The directory history can't be completely empty
554 556 self.dir_hist[:] = [py3compat.getcwd()]
555 557
556 558 if new_session:
557 559 if self.session_number:
558 560 self.end_session()
559 561 self.input_hist_parsed[:] = [""]
560 562 self.input_hist_raw[:] = [""]
561 563 self.new_session()
562 564
563 565 # ------------------------------
564 566 # Methods for retrieving history
565 567 # ------------------------------
566 568 def get_session_info(self, session=0):
567 569 """Get info about a session.
568 570
569 571 Parameters
570 572 ----------
571 573
572 574 session : int
573 575 Session number to retrieve. The current session is 0, and negative
574 576 numbers count back from current session, so -1 is the previous session.
575 577
576 578 Returns
577 579 -------
578 580
579 581 session_id : int
580 582 Session ID number
581 583 start : datetime
582 584 Timestamp for the start of the session.
583 585 end : datetime
584 586 Timestamp for the end of the session, or None if IPython crashed.
585 587 num_cmds : int
586 588 Number of commands run, or None if IPython crashed.
587 589 remark : unicode
588 590 A manually set description.
589 591 """
590 592 if session <= 0:
591 593 session += self.session_number
592 594
593 595 return super(HistoryManager, self).get_session_info(session=session)
594 596
595 597 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
596 598 """Get input and output history from the current session. Called by
597 599 get_range, and takes similar parameters."""
598 600 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
599 601
600 602 n = len(input_hist)
601 603 if start < 0:
602 604 start += n
603 605 if not stop or (stop > n):
604 606 stop = n
605 607 elif stop < 0:
606 608 stop += n
607 609
608 610 for i in range(start, stop):
609 611 if output:
610 612 line = (input_hist[i], self.output_hist_reprs.get(i))
611 613 else:
612 614 line = input_hist[i]
613 615 yield (0, i, line)
614 616
615 617 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
616 618 """Retrieve input by session.
617 619
618 620 Parameters
619 621 ----------
620 622 session : int
621 623 Session number to retrieve. The current session is 0, and negative
622 624 numbers count back from current session, so -1 is previous session.
623 625 start : int
624 626 First line to retrieve.
625 627 stop : int
626 628 End of line range (excluded from output itself). If None, retrieve
627 629 to the end of the session.
628 630 raw : bool
629 631 If True, return untranslated input
630 632 output : bool
631 633 If True, attempt to include output. This will be 'real' Python
632 634 objects for the current session, or text reprs from previous
633 635 sessions if db_log_output was enabled at the time. Where no output
634 636 is found, None is used.
635 637
636 638 Returns
637 639 -------
638 640 entries
639 641 An iterator over the desired lines. Each line is a 3-tuple, either
640 642 (session, line, input) if output is False, or
641 643 (session, line, (input, output)) if output is True.
642 644 """
643 645 if session <= 0:
644 646 session += self.session_number
645 647 if session==self.session_number: # Current session
646 648 return self._get_range_session(start, stop, raw, output)
647 649 return super(HistoryManager, self).get_range(session, start, stop, raw,
648 650 output)
649 651
650 652 ## ----------------------------
651 653 ## Methods for storing history:
652 654 ## ----------------------------
653 655 def store_inputs(self, line_num, source, source_raw=None):
654 656 """Store source and raw input in history and create input cache
655 657 variables ``_i*``.
656 658
657 659 Parameters
658 660 ----------
659 661 line_num : int
660 662 The prompt number of this input.
661 663
662 664 source : str
663 665 Python input.
664 666
665 667 source_raw : str, optional
666 668 If given, this is the raw input without any IPython transformations
667 669 applied to it. If not given, ``source`` is used.
668 670 """
669 671 if source_raw is None:
670 672 source_raw = source
671 673 source = source.rstrip('\n')
672 674 source_raw = source_raw.rstrip('\n')
673 675
674 676 # do not store exit/quit commands
675 677 if self._exit_re.match(source_raw.strip()):
676 678 return
677 679
678 680 self.input_hist_parsed.append(source)
679 681 self.input_hist_raw.append(source_raw)
680 682
681 683 with self.db_input_cache_lock:
682 684 self.db_input_cache.append((line_num, source, source_raw))
683 685 # Trigger to flush cache and write to DB.
684 686 if len(self.db_input_cache) >= self.db_cache_size:
685 687 self.save_flag.set()
686 688
687 689 # update the auto _i variables
688 690 self._iii = self._ii
689 691 self._ii = self._i
690 692 self._i = self._i00
691 693 self._i00 = source_raw
692 694
693 695 # hackish access to user namespace to create _i1,_i2... dynamically
694 696 new_i = '_i%s' % line_num
695 697 to_main = {'_i': self._i,
696 698 '_ii': self._ii,
697 699 '_iii': self._iii,
698 700 new_i : self._i00 }
699 701
700 702 if self.shell is not None:
701 703 self.shell.push(to_main, interactive=False)
702 704
703 705 def store_output(self, line_num):
704 706 """If database output logging is enabled, this saves all the
705 707 outputs from the indicated prompt number to the database. It's
706 708 called by run_cell after code has been executed.
707 709
708 710 Parameters
709 711 ----------
710 712 line_num : int
711 713 The line number from which to save outputs
712 714 """
713 715 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
714 716 return
715 717 output = self.output_hist_reprs[line_num]
716 718
717 719 with self.db_output_cache_lock:
718 720 self.db_output_cache.append((line_num, output))
719 721 if self.db_cache_size <= 1:
720 722 self.save_flag.set()
721 723
722 724 def _writeout_input_cache(self, conn):
723 725 with conn:
724 726 for line in self.db_input_cache:
725 727 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
726 728 (self.session_number,)+line)
727 729
728 730 def _writeout_output_cache(self, conn):
729 731 with conn:
730 732 for line in self.db_output_cache:
731 733 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
732 734 (self.session_number,)+line)
733 735
734 736 @needs_sqlite
735 737 def writeout_cache(self, conn=None):
736 738 """Write any entries in the cache to the database."""
737 739 if conn is None:
738 740 conn = self.db
739 741
740 742 with self.db_input_cache_lock:
741 743 try:
742 744 self._writeout_input_cache(conn)
743 745 except sqlite3.IntegrityError:
744 746 self.new_session(conn)
745 747 print("ERROR! Session/line number was not unique in",
746 748 "database. History logging moved to new session",
747 749 self.session_number)
748 750 try:
749 751 # Try writing to the new session. If this fails, don't
750 752 # recurse
751 753 self._writeout_input_cache(conn)
752 754 except sqlite3.IntegrityError:
753 755 pass
754 756 finally:
755 757 self.db_input_cache = []
756 758
757 759 with self.db_output_cache_lock:
758 760 try:
759 761 self._writeout_output_cache(conn)
760 762 except sqlite3.IntegrityError:
761 763 print("!! Session/line number for output was not unique",
762 764 "in database. Output will not be stored.")
763 765 finally:
764 766 self.db_output_cache = []
765 767
766 768
767 769 class HistorySavingThread(threading.Thread):
768 770 """This thread takes care of writing history to the database, so that
769 771 the UI isn't held up while that happens.
770 772
771 773 It waits for the HistoryManager's save_flag to be set, then writes out
772 774 the history cache. The main thread is responsible for setting the flag when
773 775 the cache size reaches a defined threshold."""
774 776 daemon = True
775 777 stop_now = False
776 778 enabled = True
777 779 def __init__(self, history_manager):
778 780 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
779 781 self.history_manager = history_manager
780 782 self.enabled = history_manager.enabled
781 783 atexit.register(self.stop)
782 784
783 785 @needs_sqlite
784 786 def run(self):
785 787 # We need a separate db connection per thread:
786 788 try:
787 789 self.db = sqlite3.connect(self.history_manager.hist_file,
788 790 **self.history_manager.connection_options
789 791 )
790 792 while True:
791 793 self.history_manager.save_flag.wait()
792 794 if self.stop_now:
793 795 self.db.close()
794 796 return
795 797 self.history_manager.save_flag.clear()
796 798 self.history_manager.writeout_cache(self.db)
797 799 except Exception as e:
798 800 print(("The history saving thread hit an unexpected error (%s)."
799 801 "History will not be written to the database.") % repr(e))
800 802
801 803 def stop(self):
802 804 """This can be called from the main thread to safely stop this thread.
803 805
804 806 Note that it does not attempt to write out remaining history before
805 807 exiting. That should be done by calling the HistoryManager's
806 808 end_session method."""
807 809 self.stop_now = True
808 810 self.history_manager.save_flag.set()
809 811 self.join()
810 812
811 813
812 814 # To match, e.g. ~5/8-~2/3
813 815 range_re = re.compile(r"""
814 816 ((?P<startsess>~?\d+)/)?
815 817 (?P<start>\d+)?
816 818 ((?P<sep>[\-:])
817 819 ((?P<endsess>~?\d+)/)?
818 820 (?P<end>\d+))?
819 821 $""", re.VERBOSE)
820 822
821 823
822 824 def extract_hist_ranges(ranges_str):
823 825 """Turn a string of history ranges into 3-tuples of (session, start, stop).
824 826
825 827 Examples
826 828 --------
827 829 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
828 830 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
829 831 """
830 832 for range_str in ranges_str.split():
831 833 rmatch = range_re.match(range_str)
832 834 if not rmatch:
833 835 continue
834 836 start = rmatch.group("start")
835 837 if start:
836 838 start = int(start)
837 839 end = rmatch.group("end")
838 840 # If no end specified, get (a, a + 1)
839 841 end = int(end) if end else start + 1
840 842 else: # start not specified
841 843 if not rmatch.group('startsess'): # no startsess
842 844 continue
843 845 start = 1
844 846 end = None # provide the entire session hist
845 847
846 848 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
847 849 end += 1
848 850 startsess = rmatch.group("startsess") or "0"
849 851 endsess = rmatch.group("endsess") or startsess
850 852 startsess = int(startsess.replace("~","-"))
851 853 endsess = int(endsess.replace("~","-"))
852 854 assert endsess >= startsess, "start session must be earlier than end session"
853 855
854 856 if endsess == startsess:
855 857 yield (startsess, start, end)
856 858 continue
857 859 # Multiple sessions in one range:
858 860 yield (startsess, start, None)
859 861 for sess in range(startsess+1, endsess):
860 862 yield (sess, 1, None)
861 863 yield (endsess, 1, end)
862 864
863 865
864 866 def _format_lineno(session, line):
865 867 """Helper function to format line numbers properly."""
866 868 if session == 0:
867 869 return str(line)
868 870 return "%s#%s" % (session, line)
869 871
870 872
1 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 1 # encoding: utf-8
2 2 """Magic functions for InteractiveShell.
3 3 """
4 4 from __future__ import print_function
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
8 8 # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
9 9 # Copyright (C) 2008 The IPython Development Team
10 10
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18 # Stdlib
19 19 import os
20 20 import re
21 21 import sys
22 22 import types
23 23 from getopt import getopt, GetoptError
24 24
25 25 # Our own
26 26 from IPython.config.configurable import Configurable
27 27 from IPython.core import oinspect
28 28 from IPython.core.error import UsageError
29 29 from IPython.core.inputsplitter import ESC_MAGIC, ESC_MAGIC2
30 30 from decorator import decorator
31 31 from IPython.utils.ipstruct import Struct
32 32 from IPython.utils.process import arg_split
33 33 from IPython.utils.py3compat import string_types, iteritems
34 34 from IPython.utils.text import dedent
35 35 from IPython.utils.traitlets import Bool, Dict, Instance, MetaHasTraits
36 36 from IPython.utils.warn import error
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Globals
40 40 #-----------------------------------------------------------------------------
41 41
42 42 # A dict we'll use for each class that has magics, used as temporary storage to
43 43 # pass information between the @line/cell_magic method decorators and the
44 44 # @magics_class class decorator, because the method decorators have no
45 45 # access to the class when they run. See for more details:
46 46 # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
47 47
48 48 magics = dict(line={}, cell={})
49 49
50 50 magic_kinds = ('line', 'cell')
51 51 magic_spec = ('line', 'cell', 'line_cell')
52 52 magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Utility classes and functions
56 56 #-----------------------------------------------------------------------------
57 57
58 58 class Bunch: pass
59 59
60 60
61 61 def on_off(tag):
62 62 """Return an ON/OFF string for a 1/0 input. Simple utility function."""
63 63 return ['OFF','ON'][tag]
64 64
65 65
66 66 def compress_dhist(dh):
67 67 """Compress a directory history into a new one with at most 20 entries.
68 68
69 69 Return a new list made from the first and last 10 elements of dhist after
70 70 removal of duplicates.
71 71 """
72 72 head, tail = dh[:-10], dh[-10:]
73 73
74 74 newhead = []
75 75 done = set()
76 76 for h in head:
77 77 if h in done:
78 78 continue
79 79 newhead.append(h)
80 80 done.add(h)
81 81
82 82 return newhead + tail
83 83
84 84
85 85 def needs_local_scope(func):
86 86 """Decorator to mark magic functions which need to local scope to run."""
87 87 func.needs_local_scope = True
88 88 return func
89 89
90 90 #-----------------------------------------------------------------------------
91 91 # Class and method decorators for registering magics
92 92 #-----------------------------------------------------------------------------
93 93
94 94 def magics_class(cls):
95 95 """Class decorator for all subclasses of the main Magics class.
96 96
97 97 Any class that subclasses Magics *must* also apply this decorator, to
98 98 ensure that all the methods that have been decorated as line/cell magics
99 99 get correctly registered in the class instance. This is necessary because
100 100 when method decorators run, the class does not exist yet, so they
101 101 temporarily store their information into a module global. Application of
102 102 this class decorator copies that global data to the class instance and
103 103 clears the global.
104 104
105 105 Obviously, this mechanism is not thread-safe, which means that the
106 106 *creation* of subclasses of Magic should only be done in a single-thread
107 107 context. Instantiation of the classes has no restrictions. Given that
108 108 these classes are typically created at IPython startup time and before user
109 109 application code becomes active, in practice this should not pose any
110 110 problems.
111 111 """
112 112 cls.registered = True
113 113 cls.magics = dict(line = magics['line'],
114 114 cell = magics['cell'])
115 115 magics['line'] = {}
116 116 magics['cell'] = {}
117 117 return cls
118 118
119 119
120 120 def record_magic(dct, magic_kind, magic_name, func):
121 121 """Utility function to store a function as a magic of a specific kind.
122 122
123 123 Parameters
124 124 ----------
125 125 dct : dict
126 126 A dictionary with 'line' and 'cell' subdicts.
127 127
128 128 magic_kind : str
129 129 Kind of magic to be stored.
130 130
131 131 magic_name : str
132 132 Key to store the magic as.
133 133
134 134 func : function
135 135 Callable object to store.
136 136 """
137 137 if magic_kind == 'line_cell':
138 138 dct['line'][magic_name] = dct['cell'][magic_name] = func
139 139 else:
140 140 dct[magic_kind][magic_name] = func
141 141
142 142
143 143 def validate_type(magic_kind):
144 144 """Ensure that the given magic_kind is valid.
145 145
146 146 Check that the given magic_kind is one of the accepted spec types (stored
147 147 in the global `magic_spec`), raise ValueError otherwise.
148 148 """
149 149 if magic_kind not in magic_spec:
150 150 raise ValueError('magic_kind must be one of %s, %s given' %
151 151 magic_kinds, magic_kind)
152 152
153 153
154 154 # The docstrings for the decorator below will be fairly similar for the two
155 155 # types (method and function), so we generate them here once and reuse the
156 156 # templates below.
157 157 _docstring_template = \
158 158 """Decorate the given {0} as {1} magic.
159 159
160 160 The decorator can be used with or without arguments, as follows.
161 161
162 162 i) without arguments: it will create a {1} magic named as the {0} being
163 163 decorated::
164 164
165 165 @deco
166 166 def foo(...)
167 167
168 168 will create a {1} magic named `foo`.
169 169
170 170 ii) with one string argument: which will be used as the actual name of the
171 171 resulting magic::
172 172
173 173 @deco('bar')
174 174 def foo(...)
175 175
176 176 will create a {1} magic named `bar`.
177 177 """
178 178
179 179 # These two are decorator factories. While they are conceptually very similar,
180 180 # there are enough differences in the details that it's simpler to have them
181 181 # written as completely standalone functions rather than trying to share code
182 182 # and make a single one with convoluted logic.
183 183
184 184 def _method_magic_marker(magic_kind):
185 185 """Decorator factory for methods in Magics subclasses.
186 186 """
187 187
188 188 validate_type(magic_kind)
189 189
190 190 # This is a closure to capture the magic_kind. We could also use a class,
191 191 # but it's overkill for just that one bit of state.
192 192 def magic_deco(arg):
193 193 call = lambda f, *a, **k: f(*a, **k)
194 194
195 195 if callable(arg):
196 196 # "Naked" decorator call (just @foo, no args)
197 197 func = arg
198 198 name = func.__name__
199 199 retval = decorator(call, func)
200 200 record_magic(magics, magic_kind, name, name)
201 201 elif isinstance(arg, string_types):
202 202 # Decorator called with arguments (@foo('bar'))
203 203 name = arg
204 204 def mark(func, *a, **kw):
205 205 record_magic(magics, magic_kind, name, func.__name__)
206 206 return decorator(call, func)
207 207 retval = mark
208 208 else:
209 209 raise TypeError("Decorator can only be called with "
210 210 "string or function")
211 211 return retval
212 212
213 213 # Ensure the resulting decorator has a usable docstring
214 214 magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
215 215 return magic_deco
216 216
217 217
218 218 def _function_magic_marker(magic_kind):
219 219 """Decorator factory for standalone functions.
220 220 """
221 221 validate_type(magic_kind)
222 222
223 223 # This is a closure to capture the magic_kind. We could also use a class,
224 224 # but it's overkill for just that one bit of state.
225 225 def magic_deco(arg):
226 226 call = lambda f, *a, **k: f(*a, **k)
227 227
228 228 # Find get_ipython() in the caller's namespace
229 229 caller = sys._getframe(1)
230 230 for ns in ['f_locals', 'f_globals', 'f_builtins']:
231 231 get_ipython = getattr(caller, ns).get('get_ipython')
232 232 if get_ipython is not None:
233 233 break
234 234 else:
235 235 raise NameError('Decorator can only run in context where '
236 236 '`get_ipython` exists')
237 237
238 238 ip = get_ipython()
239 239
240 240 if callable(arg):
241 241 # "Naked" decorator call (just @foo, no args)
242 242 func = arg
243 243 name = func.__name__
244 244 ip.register_magic_function(func, magic_kind, name)
245 245 retval = decorator(call, func)
246 246 elif isinstance(arg, string_types):
247 247 # Decorator called with arguments (@foo('bar'))
248 248 name = arg
249 249 def mark(func, *a, **kw):
250 250 ip.register_magic_function(func, magic_kind, name)
251 251 return decorator(call, func)
252 252 retval = mark
253 253 else:
254 254 raise TypeError("Decorator can only be called with "
255 255 "string or function")
256 256 return retval
257 257
258 258 # Ensure the resulting decorator has a usable docstring
259 259 ds = _docstring_template.format('function', magic_kind)
260 260
261 261 ds += dedent("""
262 262 Note: this decorator can only be used in a context where IPython is already
263 263 active, so that the `get_ipython()` call succeeds. You can therefore use
264 264 it in your startup files loaded after IPython initializes, but *not* in the
265 265 IPython configuration file itself, which is executed before IPython is
266 266 fully up and running. Any file located in the `startup` subdirectory of
267 267 your configuration profile will be OK in this sense.
268 268 """)
269 269
270 270 magic_deco.__doc__ = ds
271 271 return magic_deco
272 272
273 273
274 274 # Create the actual decorators for public use
275 275
276 276 # These three are used to decorate methods in class definitions
277 277 line_magic = _method_magic_marker('line')
278 278 cell_magic = _method_magic_marker('cell')
279 279 line_cell_magic = _method_magic_marker('line_cell')
280 280
281 281 # These three decorate standalone functions and perform the decoration
282 282 # immediately. They can only run where get_ipython() works
283 283 register_line_magic = _function_magic_marker('line')
284 284 register_cell_magic = _function_magic_marker('cell')
285 285 register_line_cell_magic = _function_magic_marker('line_cell')
286 286
287 287 #-----------------------------------------------------------------------------
288 288 # Core Magic classes
289 289 #-----------------------------------------------------------------------------
290 290
291 291 class MagicsManager(Configurable):
292 292 """Object that handles all magic-related functionality for IPython.
293 293 """
294 294 # Non-configurable class attributes
295 295
296 296 # A two-level dict, first keyed by magic type, then by magic function, and
297 297 # holding the actual callable object as value. This is the dict used for
298 298 # magic function dispatch
299 299 magics = Dict
300 300
301 301 # A registry of the original objects that we've been given holding magics.
302 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 306 auto_magic = Bool(True, config=True, help=
307 307 "Automatically call line magics without requiring explicit % prefix")
308 308
309 309 def _auto_magic_changed(self, name, value):
310 310 self.shell.automagic = value
311 311
312 312 _auto_status = [
313 313 'Automagic is OFF, % prefix IS needed for line magics.',
314 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 318 def __init__(self, shell=None, config=None, user_magics=None, **traits):
319 319
320 320 super(MagicsManager, self).__init__(shell=shell, config=config,
321 321 user_magics=user_magics, **traits)
322 322 self.magics = dict(line={}, cell={})
323 323 # Let's add the user_magics to the registry for uniformity, so *all*
324 324 # registered magic containers can be found there.
325 325 self.registry[user_magics.__class__.__name__] = user_magics
326 326
327 327 def auto_status(self):
328 328 """Return descriptive string with automagic status."""
329 329 return self._auto_status[self.auto_magic]
330 330
331 331 def lsmagic(self):
332 332 """Return a dict of currently available magic functions.
333 333
334 334 The return dict has the keys 'line' and 'cell', corresponding to the
335 335 two types of magics we support. Each value is a list of names.
336 336 """
337 337 return self.magics
338 338
339 339 def lsmagic_docs(self, brief=False, missing=''):
340 340 """Return dict of documentation of magic functions.
341 341
342 342 The return dict has the keys 'line' and 'cell', corresponding to the
343 343 two types of magics we support. Each value is a dict keyed by magic
344 344 name whose value is the function docstring. If a docstring is
345 345 unavailable, the value of `missing` is used instead.
346 346
347 347 If brief is True, only the first line of each docstring will be returned.
348 348 """
349 349 docs = {}
350 350 for m_type in self.magics:
351 351 m_docs = {}
352 352 for m_name, m_func in iteritems(self.magics[m_type]):
353 353 if m_func.__doc__:
354 354 if brief:
355 355 m_docs[m_name] = m_func.__doc__.split('\n', 1)[0]
356 356 else:
357 357 m_docs[m_name] = m_func.__doc__.rstrip()
358 358 else:
359 359 m_docs[m_name] = missing
360 360 docs[m_type] = m_docs
361 361 return docs
362 362
363 363 def register(self, *magic_objects):
364 364 """Register one or more instances of Magics.
365 365
366 366 Take one or more classes or instances of classes that subclass the main
367 367 `core.Magic` class, and register them with IPython to use the magic
368 368 functions they provide. The registration process will then ensure that
369 369 any methods that have decorated to provide line and/or cell magics will
370 370 be recognized with the `%x`/`%%x` syntax as a line/cell magic
371 371 respectively.
372 372
373 373 If classes are given, they will be instantiated with the default
374 374 constructor. If your classes need a custom constructor, you should
375 375 instanitate them first and pass the instance.
376 376
377 377 The provided arguments can be an arbitrary mix of classes and instances.
378 378
379 379 Parameters
380 380 ----------
381 381 magic_objects : one or more classes or instances
382 382 """
383 383 # Start by validating them to ensure they have all had their magic
384 384 # methods registered at the instance level
385 385 for m in magic_objects:
386 386 if not m.registered:
387 387 raise ValueError("Class of magics %r was constructed without "
388 388 "the @register_magics class decorator")
389 389 if type(m) in (type, MetaHasTraits):
390 390 # If we're given an uninstantiated class
391 391 m = m(shell=self.shell)
392 392
393 393 # Now that we have an instance, we can register it and update the
394 394 # table of callables
395 395 self.registry[m.__class__.__name__] = m
396 396 for mtype in magic_kinds:
397 397 self.magics[mtype].update(m.magics[mtype])
398 398
399 399 def register_function(self, func, magic_kind='line', magic_name=None):
400 400 """Expose a standalone function as magic function for IPython.
401 401
402 402 This will create an IPython magic (line, cell or both) from a
403 403 standalone function. The functions should have the following
404 404 signatures:
405 405
406 406 * For line magics: `def f(line)`
407 407 * For cell magics: `def f(line, cell)`
408 408 * For a function that does both: `def f(line, cell=None)`
409 409
410 410 In the latter case, the function will be called with `cell==None` when
411 411 invoked as `%f`, and with cell as a string when invoked as `%%f`.
412 412
413 413 Parameters
414 414 ----------
415 415 func : callable
416 416 Function to be registered as a magic.
417 417
418 418 magic_kind : str
419 419 Kind of magic, one of 'line', 'cell' or 'line_cell'
420 420
421 421 magic_name : optional str
422 422 If given, the name the magic will have in the IPython namespace. By
423 423 default, the name of the function itself is used.
424 424 """
425 425
426 426 # Create the new method in the user_magics and register it in the
427 427 # global table
428 428 validate_type(magic_kind)
429 429 magic_name = func.__name__ if magic_name is None else magic_name
430 430 setattr(self.user_magics, magic_name, func)
431 431 record_magic(self.magics, magic_kind, magic_name, func)
432 432
433 433 def define_magic(self, name, func):
434 434 """[Deprecated] Expose own function as magic function for IPython.
435 435
436 436 Example::
437 437
438 438 def foo_impl(self, parameter_s=''):
439 439 'My very own magic!. (Use docstrings, IPython reads them).'
440 440 print 'Magic function. Passed parameter is between < >:'
441 441 print '<%s>' % parameter_s
442 442 print 'The self object is:', self
443 443
444 444 ip.define_magic('foo',foo_impl)
445 445 """
446 446 meth = types.MethodType(func, self.user_magics)
447 447 setattr(self.user_magics, name, meth)
448 448 record_magic(self.magics, 'line', name, meth)
449 449
450 450 def register_alias(self, alias_name, magic_name, magic_kind='line'):
451 451 """Register an alias to a magic function.
452 452
453 453 The alias is an instance of :class:`MagicAlias`, which holds the
454 454 name and kind of the magic it should call. Binding is done at
455 455 call time, so if the underlying magic function is changed the alias
456 456 will call the new function.
457 457
458 458 Parameters
459 459 ----------
460 460 alias_name : str
461 461 The name of the magic to be registered.
462 462
463 463 magic_name : str
464 464 The name of an existing magic.
465 465
466 466 magic_kind : str
467 467 Kind of magic, one of 'line' or 'cell'
468 468 """
469 469
470 470 # `validate_type` is too permissive, as it allows 'line_cell'
471 471 # which we do not handle.
472 472 if magic_kind not in magic_kinds:
473 473 raise ValueError('magic_kind must be one of %s, %s given' %
474 474 magic_kinds, magic_kind)
475 475
476 476 alias = MagicAlias(self.shell, magic_name, magic_kind)
477 477 setattr(self.user_magics, alias_name, alias)
478 478 record_magic(self.magics, magic_kind, alias_name, alias)
479 479
480 480 # Key base class that provides the central functionality for magics.
481 481
482 482
483 483 class Magics(Configurable):
484 484 """Base class for implementing magic functions.
485 485
486 486 Shell functions which can be reached as %function_name. All magic
487 487 functions should accept a string, which they can parse for their own
488 488 needs. This can make some functions easier to type, eg `%cd ../`
489 489 vs. `%cd("../")`
490 490
491 491 Classes providing magic functions need to subclass this class, and they
492 492 MUST:
493 493
494 494 - Use the method decorators `@line_magic` and `@cell_magic` to decorate
495 495 individual methods as magic functions, AND
496 496
497 497 - Use the class decorator `@magics_class` to ensure that the magic
498 498 methods are properly registered at the instance level upon instance
499 499 initialization.
500 500
501 501 See :mod:`magic_functions` for examples of actual implementation classes.
502 502 """
503 503 # Dict holding all command-line options for each magic.
504 504 options_table = None
505 505 # Dict for the mapping of magic names to methods, set by class decorator
506 506 magics = None
507 507 # Flag to check that the class decorator was properly applied
508 508 registered = False
509 509 # Instance of IPython shell
510 510 shell = None
511 511
512 512 def __init__(self, shell=None, **kwargs):
513 513 if not(self.__class__.registered):
514 514 raise ValueError('Magics subclass without registration - '
515 515 'did you forget to apply @magics_class?')
516 516 if shell is not None:
517 517 if hasattr(shell, 'configurables'):
518 518 shell.configurables.append(self)
519 519 if hasattr(shell, 'config'):
520 520 kwargs.setdefault('parent', shell)
521 521 kwargs['shell'] = shell
522 522
523 523 self.shell = shell
524 524 self.options_table = {}
525 525 # The method decorators are run when the instance doesn't exist yet, so
526 526 # they can only record the names of the methods they are supposed to
527 527 # grab. Only now, that the instance exists, can we create the proper
528 528 # mapping to bound methods. So we read the info off the original names
529 529 # table and replace each method name by the actual bound method.
530 530 # But we mustn't clobber the *class* mapping, in case of multiple instances.
531 531 class_magics = self.magics
532 532 self.magics = {}
533 533 for mtype in magic_kinds:
534 534 tab = self.magics[mtype] = {}
535 535 cls_tab = class_magics[mtype]
536 536 for magic_name, meth_name in iteritems(cls_tab):
537 537 if isinstance(meth_name, string_types):
538 538 # it's a method name, grab it
539 539 tab[magic_name] = getattr(self, meth_name)
540 540 else:
541 541 # it's the real thing
542 542 tab[magic_name] = meth_name
543 543 # Configurable **needs** to be initiated at the end or the config
544 544 # magics get screwed up.
545 545 super(Magics, self).__init__(**kwargs)
546 546
547 547 def arg_err(self,func):
548 548 """Print docstring if incorrect arguments were passed"""
549 549 print('Error in arguments:')
550 550 print(oinspect.getdoc(func))
551 551
552 552 def format_latex(self, strng):
553 553 """Format a string for latex inclusion."""
554 554
555 555 # Characters that need to be escaped for latex:
556 556 escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
557 557 # Magic command names as headers:
558 558 cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
559 559 re.MULTILINE)
560 560 # Magic commands
561 561 cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
562 562 re.MULTILINE)
563 563 # Paragraph continue
564 564 par_re = re.compile(r'\\$',re.MULTILINE)
565 565
566 566 # The "\n" symbol
567 567 newline_re = re.compile(r'\\n')
568 568
569 569 # Now build the string for output:
570 570 #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
571 571 strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
572 572 strng)
573 573 strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
574 574 strng = par_re.sub(r'\\\\',strng)
575 575 strng = escape_re.sub(r'\\\1',strng)
576 576 strng = newline_re.sub(r'\\textbackslash{}n',strng)
577 577 return strng
578 578
579 579 def parse_options(self, arg_str, opt_str, *long_opts, **kw):
580 580 """Parse options passed to an argument string.
581 581
582 582 The interface is similar to that of :func:`getopt.getopt`, but it
583 583 returns a :class:`~IPython.utils.struct.Struct` with the options as keys
584 584 and the stripped argument string still as a string.
585 585
586 586 arg_str is quoted as a true sys.argv vector by using shlex.split.
587 587 This allows us to easily expand variables, glob files, quote
588 588 arguments, etc.
589 589
590 590 Parameters
591 591 ----------
592 592
593 593 arg_str : str
594 594 The arguments to parse.
595 595
596 596 opt_str : str
597 597 The options specification.
598 598
599 599 mode : str, default 'string'
600 600 If given as 'list', the argument string is returned as a list (split
601 601 on whitespace) instead of a string.
602 602
603 603 list_all : bool, default False
604 604 Put all option values in lists. Normally only options
605 605 appearing more than once are put in a list.
606 606
607 607 posix : bool, default True
608 608 Whether to split the input line in POSIX mode or not, as per the
609 609 conventions outlined in the :mod:`shlex` module from the standard
610 610 library.
611 611 """
612 612
613 613 # inject default options at the beginning of the input line
614 614 caller = sys._getframe(1).f_code.co_name
615 615 arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
616 616
617 617 mode = kw.get('mode','string')
618 618 if mode not in ['string','list']:
619 619 raise ValueError('incorrect mode given: %s' % mode)
620 620 # Get options
621 621 list_all = kw.get('list_all',0)
622 622 posix = kw.get('posix', os.name == 'posix')
623 623 strict = kw.get('strict', True)
624 624
625 625 # Check if we have more than one argument to warrant extra processing:
626 626 odict = {} # Dictionary with options
627 627 args = arg_str.split()
628 628 if len(args) >= 1:
629 629 # If the list of inputs only has 0 or 1 thing in it, there's no
630 630 # need to look for options
631 631 argv = arg_split(arg_str, posix, strict)
632 632 # Do regular option processing
633 633 try:
634 634 opts,args = getopt(argv, opt_str, long_opts)
635 635 except GetoptError as e:
636 636 raise UsageError('%s ( allowed: "%s" %s)' % (e.msg,opt_str,
637 637 " ".join(long_opts)))
638 638 for o,a in opts:
639 639 if o.startswith('--'):
640 640 o = o[2:]
641 641 else:
642 642 o = o[1:]
643 643 try:
644 644 odict[o].append(a)
645 645 except AttributeError:
646 646 odict[o] = [odict[o],a]
647 647 except KeyError:
648 648 if list_all:
649 649 odict[o] = [a]
650 650 else:
651 651 odict[o] = a
652 652
653 653 # Prepare opts,args for return
654 654 opts = Struct(odict)
655 655 if mode == 'string':
656 656 args = ' '.join(args)
657 657
658 658 return opts,args
659 659
660 660 def default_option(self, fn, optstr):
661 661 """Make an entry in the options_table for fn, with value optstr"""
662 662
663 663 if fn not in self.lsmagic():
664 664 error("%s is not a magic function" % fn)
665 665 self.options_table[fn] = optstr
666 666
667 667
668 668 class MagicAlias(object):
669 669 """An alias to another magic function.
670 670
671 671 An alias is determined by its magic name and magic kind. Lookup
672 672 is done at call time, so if the underlying magic changes the alias
673 673 will call the new function.
674 674
675 675 Use the :meth:`MagicsManager.register_alias` method or the
676 676 `%alias_magic` magic function to create and register a new alias.
677 677 """
678 678 def __init__(self, shell, magic_name, magic_kind):
679 679 self.shell = shell
680 680 self.magic_name = magic_name
681 681 self.magic_kind = magic_kind
682 682
683 683 self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name)
684 684 self.__doc__ = "Alias for `%s`." % self.pretty_target
685 685
686 686 self._in_call = False
687 687
688 688 def __call__(self, *args, **kwargs):
689 689 """Call the magic alias."""
690 690 fn = self.shell.find_magic(self.magic_name, self.magic_kind)
691 691 if fn is None:
692 692 raise UsageError("Magic `%s` not found." % self.pretty_target)
693 693
694 694 # Protect against infinite recursion.
695 695 if self._in_call:
696 696 raise UsageError("Infinite recursion detected; "
697 697 "magic aliases cannot call themselves.")
698 698 self._in_call = True
699 699 try:
700 700 return fn(*args, **kwargs)
701 701 finally:
702 702 self._in_call = False
@@ -1,715 +1,715 b''
1 1 # encoding: utf-8
2 2 """
3 3 Prefiltering components.
4 4
5 5 Prefilters transform user input before it is exec'd by Python. These
6 6 transforms are used to implement additional syntax such as !ls and %magic.
7 7
8 8 Authors:
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Dan Milstein
13 13 * Ville Vainio
14 14 """
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008-2011 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 from keyword import iskeyword
28 28 import re
29 29
30 30 from IPython.core.autocall import IPyAutocall
31 31 from IPython.config.configurable import Configurable
32 32 from IPython.core.inputsplitter import (
33 33 ESC_MAGIC,
34 34 ESC_QUOTE,
35 35 ESC_QUOTE2,
36 36 ESC_PAREN,
37 37 )
38 38 from IPython.core.macro import Macro
39 39 from IPython.core.splitinput import LineInfo
40 40
41 41 from IPython.utils.traitlets import (
42 42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
43 43 )
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Global utilities, errors and constants
47 47 #-----------------------------------------------------------------------------
48 48
49 49
50 50 class PrefilterError(Exception):
51 51 pass
52 52
53 53
54 54 # RegExp to identify potential function names
55 55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
56 56
57 57 # RegExp to exclude strings with this start from autocalling. In
58 58 # particular, all binary operators should be excluded, so that if foo is
59 59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
60 60 # characters '!=()' don't need to be checked for, as the checkPythonChars
61 61 # routine explicitely does so, to catch direct calls and rebindings of
62 62 # existing names.
63 63
64 64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
65 65 # it affects the rest of the group in square brackets.
66 66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
67 67 r'|^is |^not |^in |^and |^or ')
68 68
69 69 # try to catch also methods for stuff in lists/tuples/dicts: off
70 70 # (experimental). For this to work, the line_split regexp would need
71 71 # to be modified so it wouldn't break things at '['. That line is
72 72 # nasty enough that I shouldn't change it until I can test it _well_.
73 73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
74 74
75 75
76 76 # Handler Check Utilities
77 77 def is_shadowed(identifier, ip):
78 78 """Is the given identifier defined in one of the namespaces which shadow
79 79 the alias and magic namespaces? Note that an identifier is different
80 80 than ifun, because it can not contain a '.' character."""
81 81 # This is much safer than calling ofind, which can change state
82 82 return (identifier in ip.user_ns \
83 83 or identifier in ip.user_global_ns \
84 84 or identifier in ip.ns_table['builtin']\
85 85 or iskeyword(identifier))
86 86
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Main Prefilter manager
90 90 #-----------------------------------------------------------------------------
91 91
92 92
93 93 class PrefilterManager(Configurable):
94 94 """Main prefilter component.
95 95
96 96 The IPython prefilter is run on all user input before it is run. The
97 97 prefilter consumes lines of input and produces transformed lines of
98 98 input.
99 99
100 100 The iplementation consists of two phases:
101 101
102 102 1. Transformers
103 103 2. Checkers and handlers
104 104
105 105 Over time, we plan on deprecating the checkers and handlers and doing
106 106 everything in the transformers.
107 107
108 108 The transformers are instances of :class:`PrefilterTransformer` and have
109 109 a single method :meth:`transform` that takes a line and returns a
110 110 transformed line. The transformation can be accomplished using any
111 111 tool, but our current ones use regular expressions for speed.
112 112
113 113 After all the transformers have been run, the line is fed to the checkers,
114 114 which are instances of :class:`PrefilterChecker`. The line is passed to
115 115 the :meth:`check` method, which either returns `None` or a
116 116 :class:`PrefilterHandler` instance. If `None` is returned, the other
117 117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
118 118 the line is passed to the :meth:`handle` method of the returned
119 119 handler and no further checkers are tried.
120 120
121 121 Both transformers and checkers have a `priority` attribute, that determines
122 122 the order in which they are called. Smaller priorities are tried first.
123 123
124 124 Both transformers and checkers also have `enabled` attribute, which is
125 125 a boolean that determines if the instance is used.
126 126
127 127 Users or developers can change the priority or enabled attribute of
128 128 transformers or checkers, but they must call the :meth:`sort_checkers`
129 129 or :meth:`sort_transformers` method after changing the priority.
130 130 """
131 131
132 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 135 def __init__(self, shell=None, **kwargs):
136 136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
137 137 self.shell = shell
138 138 self.init_transformers()
139 139 self.init_handlers()
140 140 self.init_checkers()
141 141
142 142 #-------------------------------------------------------------------------
143 143 # API for managing transformers
144 144 #-------------------------------------------------------------------------
145 145
146 146 def init_transformers(self):
147 147 """Create the default transformers."""
148 148 self._transformers = []
149 149 for transformer_cls in _default_transformers:
150 150 transformer_cls(
151 151 shell=self.shell, prefilter_manager=self, parent=self
152 152 )
153 153
154 154 def sort_transformers(self):
155 155 """Sort the transformers by priority.
156 156
157 157 This must be called after the priority of a transformer is changed.
158 158 The :meth:`register_transformer` method calls this automatically.
159 159 """
160 160 self._transformers.sort(key=lambda x: x.priority)
161 161
162 162 @property
163 163 def transformers(self):
164 164 """Return a list of checkers, sorted by priority."""
165 165 return self._transformers
166 166
167 167 def register_transformer(self, transformer):
168 168 """Register a transformer instance."""
169 169 if transformer not in self._transformers:
170 170 self._transformers.append(transformer)
171 171 self.sort_transformers()
172 172
173 173 def unregister_transformer(self, transformer):
174 174 """Unregister a transformer instance."""
175 175 if transformer in self._transformers:
176 176 self._transformers.remove(transformer)
177 177
178 178 #-------------------------------------------------------------------------
179 179 # API for managing checkers
180 180 #-------------------------------------------------------------------------
181 181
182 182 def init_checkers(self):
183 183 """Create the default checkers."""
184 184 self._checkers = []
185 185 for checker in _default_checkers:
186 186 checker(
187 187 shell=self.shell, prefilter_manager=self, parent=self
188 188 )
189 189
190 190 def sort_checkers(self):
191 191 """Sort the checkers by priority.
192 192
193 193 This must be called after the priority of a checker is changed.
194 194 The :meth:`register_checker` method calls this automatically.
195 195 """
196 196 self._checkers.sort(key=lambda x: x.priority)
197 197
198 198 @property
199 199 def checkers(self):
200 200 """Return a list of checkers, sorted by priority."""
201 201 return self._checkers
202 202
203 203 def register_checker(self, checker):
204 204 """Register a checker instance."""
205 205 if checker not in self._checkers:
206 206 self._checkers.append(checker)
207 207 self.sort_checkers()
208 208
209 209 def unregister_checker(self, checker):
210 210 """Unregister a checker instance."""
211 211 if checker in self._checkers:
212 212 self._checkers.remove(checker)
213 213
214 214 #-------------------------------------------------------------------------
215 215 # API for managing handlers
216 216 #-------------------------------------------------------------------------
217 217
218 218 def init_handlers(self):
219 219 """Create the default handlers."""
220 220 self._handlers = {}
221 221 self._esc_handlers = {}
222 222 for handler in _default_handlers:
223 223 handler(
224 224 shell=self.shell, prefilter_manager=self, parent=self
225 225 )
226 226
227 227 @property
228 228 def handlers(self):
229 229 """Return a dict of all the handlers."""
230 230 return self._handlers
231 231
232 232 def register_handler(self, name, handler, esc_strings):
233 233 """Register a handler instance by name with esc_strings."""
234 234 self._handlers[name] = handler
235 235 for esc_str in esc_strings:
236 236 self._esc_handlers[esc_str] = handler
237 237
238 238 def unregister_handler(self, name, handler, esc_strings):
239 239 """Unregister a handler instance by name with esc_strings."""
240 240 try:
241 241 del self._handlers[name]
242 242 except KeyError:
243 243 pass
244 244 for esc_str in esc_strings:
245 245 h = self._esc_handlers.get(esc_str)
246 246 if h is handler:
247 247 del self._esc_handlers[esc_str]
248 248
249 249 def get_handler_by_name(self, name):
250 250 """Get a handler by its name."""
251 251 return self._handlers.get(name)
252 252
253 253 def get_handler_by_esc(self, esc_str):
254 254 """Get a handler by its escape string."""
255 255 return self._esc_handlers.get(esc_str)
256 256
257 257 #-------------------------------------------------------------------------
258 258 # Main prefiltering API
259 259 #-------------------------------------------------------------------------
260 260
261 261 def prefilter_line_info(self, line_info):
262 262 """Prefilter a line that has been converted to a LineInfo object.
263 263
264 264 This implements the checker/handler part of the prefilter pipe.
265 265 """
266 266 # print "prefilter_line_info: ", line_info
267 267 handler = self.find_handler(line_info)
268 268 return handler.handle(line_info)
269 269
270 270 def find_handler(self, line_info):
271 271 """Find a handler for the line_info by trying checkers."""
272 272 for checker in self.checkers:
273 273 if checker.enabled:
274 274 handler = checker.check(line_info)
275 275 if handler:
276 276 return handler
277 277 return self.get_handler_by_name('normal')
278 278
279 279 def transform_line(self, line, continue_prompt):
280 280 """Calls the enabled transformers in order of increasing priority."""
281 281 for transformer in self.transformers:
282 282 if transformer.enabled:
283 283 line = transformer.transform(line, continue_prompt)
284 284 return line
285 285
286 286 def prefilter_line(self, line, continue_prompt=False):
287 287 """Prefilter a single input line as text.
288 288
289 289 This method prefilters a single line of text by calling the
290 290 transformers and then the checkers/handlers.
291 291 """
292 292
293 293 # print "prefilter_line: ", line, continue_prompt
294 294 # All handlers *must* return a value, even if it's blank ('').
295 295
296 296 # save the line away in case we crash, so the post-mortem handler can
297 297 # record it
298 298 self.shell._last_input_line = line
299 299
300 300 if not line:
301 301 # Return immediately on purely empty lines, so that if the user
302 302 # previously typed some whitespace that started a continuation
303 303 # prompt, he can break out of that loop with just an empty line.
304 304 # This is how the default python prompt works.
305 305 return ''
306 306
307 307 # At this point, we invoke our transformers.
308 308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
309 309 line = self.transform_line(line, continue_prompt)
310 310
311 311 # Now we compute line_info for the checkers and handlers
312 312 line_info = LineInfo(line, continue_prompt)
313 313
314 314 # the input history needs to track even empty lines
315 315 stripped = line.strip()
316 316
317 317 normal_handler = self.get_handler_by_name('normal')
318 318 if not stripped:
319 319 return normal_handler.handle(line_info)
320 320
321 321 # special handlers are only allowed for single line statements
322 322 if continue_prompt and not self.multi_line_specials:
323 323 return normal_handler.handle(line_info)
324 324
325 325 prefiltered = self.prefilter_line_info(line_info)
326 326 # print "prefiltered line: %r" % prefiltered
327 327 return prefiltered
328 328
329 329 def prefilter_lines(self, lines, continue_prompt=False):
330 330 """Prefilter multiple input lines of text.
331 331
332 332 This is the main entry point for prefiltering multiple lines of
333 333 input. This simply calls :meth:`prefilter_line` for each line of
334 334 input.
335 335
336 336 This covers cases where there are multiple lines in the user entry,
337 337 which is the case when the user goes back to a multiline history
338 338 entry and presses enter.
339 339 """
340 340 llines = lines.rstrip('\n').split('\n')
341 341 # We can get multiple lines in one shot, where multiline input 'blends'
342 342 # into one line, in cases like recalling from the readline history
343 343 # buffer. We need to make sure that in such cases, we correctly
344 344 # communicate downstream which line is first and which are continuation
345 345 # ones.
346 346 if len(llines) > 1:
347 347 out = '\n'.join([self.prefilter_line(line, lnum>0)
348 348 for lnum, line in enumerate(llines) ])
349 349 else:
350 350 out = self.prefilter_line(llines[0], continue_prompt)
351 351
352 352 return out
353 353
354 354 #-----------------------------------------------------------------------------
355 355 # Prefilter transformers
356 356 #-----------------------------------------------------------------------------
357 357
358 358
359 359 class PrefilterTransformer(Configurable):
360 360 """Transform a line of user input."""
361 361
362 362 priority = Integer(100, config=True)
363 363 # Transformers don't currently use shell or prefilter_manager, but as we
364 364 # move away from checkers and handlers, they will need them.
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
367 367 enabled = Bool(True, config=True)
368 368
369 369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
370 370 super(PrefilterTransformer, self).__init__(
371 371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
372 372 )
373 373 self.prefilter_manager.register_transformer(self)
374 374
375 375 def transform(self, line, continue_prompt):
376 376 """Transform a line, returning the new one."""
377 377 return None
378 378
379 379 def __repr__(self):
380 380 return "<%s(priority=%r, enabled=%r)>" % (
381 381 self.__class__.__name__, self.priority, self.enabled)
382 382
383 383
384 384 #-----------------------------------------------------------------------------
385 385 # Prefilter checkers
386 386 #-----------------------------------------------------------------------------
387 387
388 388
389 389 class PrefilterChecker(Configurable):
390 390 """Inspect an input line and return a handler for that line."""
391 391
392 392 priority = Integer(100, config=True)
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
395 395 enabled = Bool(True, config=True)
396 396
397 397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
398 398 super(PrefilterChecker, self).__init__(
399 399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
400 400 )
401 401 self.prefilter_manager.register_checker(self)
402 402
403 403 def check(self, line_info):
404 404 """Inspect line_info and return a handler instance or None."""
405 405 return None
406 406
407 407 def __repr__(self):
408 408 return "<%s(priority=%r, enabled=%r)>" % (
409 409 self.__class__.__name__, self.priority, self.enabled)
410 410
411 411
412 412 class EmacsChecker(PrefilterChecker):
413 413
414 414 priority = Integer(100, config=True)
415 415 enabled = Bool(False, config=True)
416 416
417 417 def check(self, line_info):
418 418 "Emacs ipython-mode tags certain input lines."
419 419 if line_info.line.endswith('# PYTHON-MODE'):
420 420 return self.prefilter_manager.get_handler_by_name('emacs')
421 421 else:
422 422 return None
423 423
424 424
425 425 class MacroChecker(PrefilterChecker):
426 426
427 427 priority = Integer(250, config=True)
428 428
429 429 def check(self, line_info):
430 430 obj = self.shell.user_ns.get(line_info.ifun)
431 431 if isinstance(obj, Macro):
432 432 return self.prefilter_manager.get_handler_by_name('macro')
433 433 else:
434 434 return None
435 435
436 436
437 437 class IPyAutocallChecker(PrefilterChecker):
438 438
439 439 priority = Integer(300, config=True)
440 440
441 441 def check(self, line_info):
442 442 "Instances of IPyAutocall in user_ns get autocalled immediately"
443 443 obj = self.shell.user_ns.get(line_info.ifun, None)
444 444 if isinstance(obj, IPyAutocall):
445 445 obj.set_ip(self.shell)
446 446 return self.prefilter_manager.get_handler_by_name('auto')
447 447 else:
448 448 return None
449 449
450 450
451 451 class AssignmentChecker(PrefilterChecker):
452 452
453 453 priority = Integer(600, config=True)
454 454
455 455 def check(self, line_info):
456 456 """Check to see if user is assigning to a var for the first time, in
457 457 which case we want to avoid any sort of automagic / autocall games.
458 458
459 459 This allows users to assign to either alias or magic names true python
460 460 variables (the magic/alias systems always take second seat to true
461 461 python code). E.g. ls='hi', or ls,that=1,2"""
462 462 if line_info.the_rest:
463 463 if line_info.the_rest[0] in '=,':
464 464 return self.prefilter_manager.get_handler_by_name('normal')
465 465 else:
466 466 return None
467 467
468 468
469 469 class AutoMagicChecker(PrefilterChecker):
470 470
471 471 priority = Integer(700, config=True)
472 472
473 473 def check(self, line_info):
474 474 """If the ifun is magic, and automagic is on, run it. Note: normal,
475 475 non-auto magic would already have been triggered via '%' in
476 476 check_esc_chars. This just checks for automagic. Also, before
477 477 triggering the magic handler, make sure that there is nothing in the
478 478 user namespace which could shadow it."""
479 479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
480 480 return None
481 481
482 482 # We have a likely magic method. Make sure we should actually call it.
483 483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
484 484 return None
485 485
486 486 head = line_info.ifun.split('.',1)[0]
487 487 if is_shadowed(head, self.shell):
488 488 return None
489 489
490 490 return self.prefilter_manager.get_handler_by_name('magic')
491 491
492 492
493 493 class PythonOpsChecker(PrefilterChecker):
494 494
495 495 priority = Integer(900, config=True)
496 496
497 497 def check(self, line_info):
498 498 """If the 'rest' of the line begins with a function call or pretty much
499 499 any python operator, we should simply execute the line (regardless of
500 500 whether or not there's a possible autocall expansion). This avoids
501 501 spurious (and very confusing) geattr() accesses."""
502 502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
503 503 return self.prefilter_manager.get_handler_by_name('normal')
504 504 else:
505 505 return None
506 506
507 507
508 508 class AutocallChecker(PrefilterChecker):
509 509
510 510 priority = Integer(1000, config=True)
511 511
512 512 function_name_regexp = CRegExp(re_fun_name, config=True,
513 513 help="RegExp to identify potential function names.")
514 514 exclude_regexp = CRegExp(re_exclude_auto, config=True,
515 515 help="RegExp to exclude strings with this start from autocalling.")
516 516
517 517 def check(self, line_info):
518 518 "Check if the initial word/function is callable and autocall is on."
519 519 if not self.shell.autocall:
520 520 return None
521 521
522 522 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
523 523 if not oinfo['found']:
524 524 return None
525 525
526 526 if callable(oinfo['obj']) \
527 527 and (not self.exclude_regexp.match(line_info.the_rest)) \
528 528 and self.function_name_regexp.match(line_info.ifun):
529 529 return self.prefilter_manager.get_handler_by_name('auto')
530 530 else:
531 531 return None
532 532
533 533
534 534 #-----------------------------------------------------------------------------
535 535 # Prefilter handlers
536 536 #-----------------------------------------------------------------------------
537 537
538 538
539 539 class PrefilterHandler(Configurable):
540 540
541 541 handler_name = Unicode('normal')
542 542 esc_strings = List([])
543 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
544 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager')
543 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
544 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
545 545
546 546 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
547 547 super(PrefilterHandler, self).__init__(
548 548 shell=shell, prefilter_manager=prefilter_manager, **kwargs
549 549 )
550 550 self.prefilter_manager.register_handler(
551 551 self.handler_name,
552 552 self,
553 553 self.esc_strings
554 554 )
555 555
556 556 def handle(self, line_info):
557 557 # print "normal: ", line_info
558 558 """Handle normal input lines. Use as a template for handlers."""
559 559
560 560 # With autoindent on, we need some way to exit the input loop, and I
561 561 # don't want to force the user to have to backspace all the way to
562 562 # clear the line. The rule will be in this case, that either two
563 563 # lines of pure whitespace in a row, or a line of pure whitespace but
564 564 # of a size different to the indent level, will exit the input loop.
565 565 line = line_info.line
566 566 continue_prompt = line_info.continue_prompt
567 567
568 568 if (continue_prompt and
569 569 self.shell.autoindent and
570 570 line.isspace() and
571 571 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
572 572 line = ''
573 573
574 574 return line
575 575
576 576 def __str__(self):
577 577 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
578 578
579 579
580 580 class MacroHandler(PrefilterHandler):
581 581 handler_name = Unicode("macro")
582 582
583 583 def handle(self, line_info):
584 584 obj = self.shell.user_ns.get(line_info.ifun)
585 585 pre_space = line_info.pre_whitespace
586 586 line_sep = "\n" + pre_space
587 587 return pre_space + line_sep.join(obj.value.splitlines())
588 588
589 589
590 590 class MagicHandler(PrefilterHandler):
591 591
592 592 handler_name = Unicode('magic')
593 593 esc_strings = List([ESC_MAGIC])
594 594
595 595 def handle(self, line_info):
596 596 """Execute magic functions."""
597 597 ifun = line_info.ifun
598 598 the_rest = line_info.the_rest
599 599 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
600 600 (ifun + " " + the_rest))
601 601 return cmd
602 602
603 603
604 604 class AutoHandler(PrefilterHandler):
605 605
606 606 handler_name = Unicode('auto')
607 607 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
608 608
609 609 def handle(self, line_info):
610 610 """Handle lines which can be auto-executed, quoting if requested."""
611 611 line = line_info.line
612 612 ifun = line_info.ifun
613 613 the_rest = line_info.the_rest
614 614 pre = line_info.pre
615 615 esc = line_info.esc
616 616 continue_prompt = line_info.continue_prompt
617 617 obj = line_info.ofind(self.shell)['obj']
618 618 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun,the_rest) # dbg
619 619
620 620 # This should only be active for single-line input!
621 621 if continue_prompt:
622 622 return line
623 623
624 624 force_auto = isinstance(obj, IPyAutocall)
625 625
626 626 # User objects sometimes raise exceptions on attribute access other
627 627 # than AttributeError (we've seen it in the past), so it's safest to be
628 628 # ultra-conservative here and catch all.
629 629 try:
630 630 auto_rewrite = obj.rewrite
631 631 except Exception:
632 632 auto_rewrite = True
633 633
634 634 if esc == ESC_QUOTE:
635 635 # Auto-quote splitting on whitespace
636 636 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
637 637 elif esc == ESC_QUOTE2:
638 638 # Auto-quote whole string
639 639 newcmd = '%s("%s")' % (ifun,the_rest)
640 640 elif esc == ESC_PAREN:
641 641 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
642 642 else:
643 643 # Auto-paren.
644 644 if force_auto:
645 645 # Don't rewrite if it is already a call.
646 646 do_rewrite = not the_rest.startswith('(')
647 647 else:
648 648 if not the_rest:
649 649 # We only apply it to argument-less calls if the autocall
650 650 # parameter is set to 2.
651 651 do_rewrite = (self.shell.autocall >= 2)
652 652 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
653 653 # Don't autocall in this case: item access for an object
654 654 # which is BOTH callable and implements __getitem__.
655 655 do_rewrite = False
656 656 else:
657 657 do_rewrite = True
658 658
659 659 # Figure out the rewritten command
660 660 if do_rewrite:
661 661 if the_rest.endswith(';'):
662 662 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
663 663 else:
664 664 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
665 665 else:
666 666 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
667 667 return normal_handler.handle(line_info)
668 668
669 669 # Display the rewritten call
670 670 if auto_rewrite:
671 671 self.shell.auto_rewrite_input(newcmd)
672 672
673 673 return newcmd
674 674
675 675
676 676 class EmacsHandler(PrefilterHandler):
677 677
678 678 handler_name = Unicode('emacs')
679 679 esc_strings = List([])
680 680
681 681 def handle(self, line_info):
682 682 """Handle input lines marked by python-mode."""
683 683
684 684 # Currently, nothing is done. Later more functionality can be added
685 685 # here if needed.
686 686
687 687 # The input cache shouldn't be updated
688 688 return line_info.line
689 689
690 690
691 691 #-----------------------------------------------------------------------------
692 692 # Defaults
693 693 #-----------------------------------------------------------------------------
694 694
695 695
696 696 _default_transformers = [
697 697 ]
698 698
699 699 _default_checkers = [
700 700 EmacsChecker,
701 701 MacroChecker,
702 702 IPyAutocallChecker,
703 703 AssignmentChecker,
704 704 AutoMagicChecker,
705 705 PythonOpsChecker,
706 706 AutocallChecker
707 707 ]
708 708
709 709 _default_handlers = [
710 710 PrefilterHandler,
711 711 MacroHandler,
712 712 MagicHandler,
713 713 AutoHandler,
714 714 EmacsHandler
715 715 ]
@@ -1,442 +1,442 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Classes for handling input/output prompts.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian Granger
8 8 * Thomas Kluyver
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import re
25 25 import socket
26 26 import sys
27 27 import time
28 28
29 29 from string import Formatter
30 30
31 31 from IPython.config.configurable import Configurable
32 32 from IPython.core import release
33 33 from IPython.utils import coloransi, py3compat
34 34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Color schemes for prompts
38 38 #-----------------------------------------------------------------------------
39 39
40 40 InputColors = coloransi.InputTermColors # just a shorthand
41 41 Colors = coloransi.TermColors # just a shorthand
42 42
43 43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44 44
45 45 PColNoColors = coloransi.ColorScheme(
46 46 'NoColor',
47 47 in_prompt = InputColors.NoColor, # Input prompt
48 48 in_number = InputColors.NoColor, # Input prompt number
49 49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51 51
52 52 out_prompt = Colors.NoColor, # Output prompt
53 53 out_number = Colors.NoColor, # Output prompt number
54 54
55 55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 56 )
57 57
58 58 # make some schemes as instances so we can copy them for modification easily:
59 59 PColLinux = coloransi.ColorScheme(
60 60 'Linux',
61 61 in_prompt = InputColors.Green,
62 62 in_number = InputColors.LightGreen,
63 63 in_prompt2 = InputColors.Green,
64 64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65 65
66 66 out_prompt = Colors.Red,
67 67 out_number = Colors.LightRed,
68 68
69 69 normal = Colors.Normal
70 70 )
71 71
72 72 # Slightly modified Linux for light backgrounds
73 73 PColLightBG = PColLinux.copy('LightBG')
74 74
75 75 PColLightBG.colors.update(
76 76 in_prompt = InputColors.Blue,
77 77 in_number = InputColors.LightBlue,
78 78 in_prompt2 = InputColors.Blue
79 79 )
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Utilities
83 83 #-----------------------------------------------------------------------------
84 84
85 85 class LazyEvaluate(object):
86 86 """This is used for formatting strings with values that need to be updated
87 87 at that time, such as the current time or working directory."""
88 88 def __init__(self, func, *args, **kwargs):
89 89 self.func = func
90 90 self.args = args
91 91 self.kwargs = kwargs
92 92
93 93 def __call__(self, **kwargs):
94 94 self.kwargs.update(kwargs)
95 95 return self.func(*self.args, **self.kwargs)
96 96
97 97 def __str__(self):
98 98 return str(self())
99 99
100 100 def __unicode__(self):
101 101 return py3compat.unicode_type(self())
102 102
103 103 def __format__(self, format_spec):
104 104 return format(self(), format_spec)
105 105
106 106 def multiple_replace(dict, text):
107 107 """ Replace in 'text' all occurences of any key in the given
108 108 dictionary by its corresponding value. Returns the new string."""
109 109
110 110 # Function by Xavier Defrang, originally found at:
111 111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112 112
113 113 # Create a regular expression from the dictionary keys
114 114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 115 # For each match, look-up corresponding value in dictionary
116 116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117 117
118 118 #-----------------------------------------------------------------------------
119 119 # Special characters that can be used in prompt templates, mainly bash-like
120 120 #-----------------------------------------------------------------------------
121 121
122 122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
123 123 # never be expanded out into '~'. Basically anything which can never be a
124 124 # reasonable directory name will do, we just want the $HOME -> '~' operation
125 125 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 126 # prompt call.
127 127
128 128 # FIXME:
129 129
130 130 # - This should be turned into a class which does proper namespace management,
131 131 # since the prompt specials need to be evaluated in a certain namespace.
132 132 # Currently it's just globals, which need to be managed manually by code
133 133 # below.
134 134
135 135 # - I also need to split up the color schemes from the prompt specials
136 136 # somehow. I don't have a clean design for that quite yet.
137 137
138 138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139 139
140 140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
141 141 # /usr/home, but retain the $HOME variable as pointing to /home
142 142 HOME = os.path.realpath(HOME)
143 143
144 144 # We precompute a few more strings here for the prompt_specials, which are
145 145 # fixed once ipython starts. This reduces the runtime overhead of computing
146 146 # prompt strings.
147 147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150 150
151 151 # IronPython doesn't currently have os.getuid() even if
152 152 # os.name == 'posix'; 2/8/2014
153 153 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
154 154
155 155 prompt_abbreviations = {
156 156 # Prompt/history count
157 157 '%n' : '{color.number}' '{count}' '{color.prompt}',
158 158 r'\#': '{color.number}' '{count}' '{color.prompt}',
159 159 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
160 160 # can get numbers displayed in whatever color they want.
161 161 r'\N': '{count}',
162 162
163 163 # Prompt/history count, with the actual digits replaced by dots. Used
164 164 # mainly in continuation prompts (prompt_in2)
165 165 r'\D': '{dots}',
166 166
167 167 # Current time
168 168 r'\T' : '{time}',
169 169 # Current working directory
170 170 r'\w': '{cwd}',
171 171 # Basename of current working directory.
172 172 # (use os.sep to make this portable across OSes)
173 173 r'\W' : '{cwd_last}',
174 174 # These X<N> are an extension to the normal bash prompts. They return
175 175 # N terms of the path, after replacing $HOME with '~'
176 176 r'\X0': '{cwd_x[0]}',
177 177 r'\X1': '{cwd_x[1]}',
178 178 r'\X2': '{cwd_x[2]}',
179 179 r'\X3': '{cwd_x[3]}',
180 180 r'\X4': '{cwd_x[4]}',
181 181 r'\X5': '{cwd_x[5]}',
182 182 # Y<N> are similar to X<N>, but they show '~' if it's the directory
183 183 # N+1 in the list. Somewhat like %cN in tcsh.
184 184 r'\Y0': '{cwd_y[0]}',
185 185 r'\Y1': '{cwd_y[1]}',
186 186 r'\Y2': '{cwd_y[2]}',
187 187 r'\Y3': '{cwd_y[3]}',
188 188 r'\Y4': '{cwd_y[4]}',
189 189 r'\Y5': '{cwd_y[5]}',
190 190 # Hostname up to first .
191 191 r'\h': HOSTNAME_SHORT,
192 192 # Full hostname
193 193 r'\H': HOSTNAME,
194 194 # Username of current user
195 195 r'\u': USER,
196 196 # Escaped '\'
197 197 '\\\\': '\\',
198 198 # Newline
199 199 r'\n': '\n',
200 200 # Carriage return
201 201 r'\r': '\r',
202 202 # Release version
203 203 r'\v': release.version,
204 204 # Root symbol ($ or #)
205 205 r'\$': ROOT_SYMBOL,
206 206 }
207 207
208 208 #-----------------------------------------------------------------------------
209 209 # More utilities
210 210 #-----------------------------------------------------------------------------
211 211
212 212 def cwd_filt(depth):
213 213 """Return the last depth elements of the current working directory.
214 214
215 215 $HOME is always replaced with '~'.
216 216 If depth==0, the full path is returned."""
217 217
218 218 cwd = py3compat.getcwd().replace(HOME,"~")
219 219 out = os.sep.join(cwd.split(os.sep)[-depth:])
220 220 return out or os.sep
221 221
222 222 def cwd_filt2(depth):
223 223 """Return the last depth elements of the current working directory.
224 224
225 225 $HOME is always replaced with '~'.
226 226 If depth==0, the full path is returned."""
227 227
228 228 full_cwd = py3compat.getcwd()
229 229 cwd = full_cwd.replace(HOME,"~").split(os.sep)
230 230 if '~' in cwd and len(cwd) == depth+1:
231 231 depth += 1
232 232 drivepart = ''
233 233 if sys.platform == 'win32' and len(cwd) > depth:
234 234 drivepart = os.path.splitdrive(full_cwd)[0]
235 235 out = drivepart + '/'.join(cwd[-depth:])
236 236
237 237 return out or os.sep
238 238
239 239 #-----------------------------------------------------------------------------
240 240 # Prompt classes
241 241 #-----------------------------------------------------------------------------
242 242
243 243 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
244 244 'cwd': LazyEvaluate(py3compat.getcwd),
245 245 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
246 246 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
247 247 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
248 248 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
249 249 }
250 250
251 251 def _lenlastline(s):
252 252 """Get the length of the last line. More intelligent than
253 253 len(s.splitlines()[-1]).
254 254 """
255 255 if not s or s.endswith(('\n', '\r')):
256 256 return 0
257 257 return len(s.splitlines()[-1])
258 258
259 259
260 260 class UserNSFormatter(Formatter):
261 261 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
262 262 def __init__(self, shell):
263 263 self.shell = shell
264 264
265 265 def get_value(self, key, args, kwargs):
266 266 # try regular formatting first:
267 267 try:
268 268 return Formatter.get_value(self, key, args, kwargs)
269 269 except Exception:
270 270 pass
271 271 # next, look in user_ns and builtins:
272 272 for container in (self.shell.user_ns, __builtins__):
273 273 if key in container:
274 274 return container[key]
275 275 # nothing found, put error message in its place
276 276 return "<ERROR: '%s' not found>" % key
277 277
278 278
279 279 class PromptManager(Configurable):
280 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 284 color_scheme = Unicode('Linux', config=True)
285 285 def _color_scheme_changed(self, name, new_value):
286 286 self.color_scheme_table.set_active_scheme(new_value)
287 287 for pname in ['in', 'in2', 'out', 'rewrite']:
288 288 # We need to recalculate the number of invisible characters
289 289 self.update_prompt(pname)
290 290
291 291 lazy_evaluate_fields = Dict(help="""
292 292 This maps field names used in the prompt templates to functions which
293 293 will be called when the prompt is rendered. This allows us to include
294 294 things like the current time in the prompts. Functions are only called
295 295 if they are used in the prompt.
296 296 """)
297 297 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
298 298
299 299 in_template = Unicode('In [\\#]: ', config=True,
300 300 help="Input prompt. '\\#' will be transformed to the prompt number")
301 301 in2_template = Unicode(' .\\D.: ', config=True,
302 302 help="Continuation prompt.")
303 303 out_template = Unicode('Out[\\#]: ', config=True,
304 304 help="Output prompt. '\\#' will be transformed to the prompt number")
305 305
306 306 justify = Bool(True, config=True, help="""
307 307 If True (default), each prompt will be right-aligned with the
308 308 preceding one.
309 309 """)
310 310
311 311 # We actually store the expanded templates here:
312 312 templates = Dict()
313 313
314 314 # The number of characters in the last prompt rendered, not including
315 315 # colour characters.
316 316 width = Int()
317 317 txtwidth = Int() # Not including right-justification
318 318
319 319 # The number of characters in each prompt which don't contribute to width
320 320 invisible_chars = Dict()
321 321 def _invisible_chars_default(self):
322 322 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
323 323
324 324 def __init__(self, shell, **kwargs):
325 325 super(PromptManager, self).__init__(shell=shell, **kwargs)
326 326
327 327 # Prepare colour scheme table
328 328 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
329 329 PColLinux, PColLightBG], self.color_scheme)
330 330
331 331 self._formatter = UserNSFormatter(shell)
332 332 # Prepare templates & numbers of invisible characters
333 333 self.update_prompt('in', self.in_template)
334 334 self.update_prompt('in2', self.in2_template)
335 335 self.update_prompt('out', self.out_template)
336 336 self.update_prompt('rewrite')
337 337 self.on_trait_change(self._update_prompt_trait, ['in_template',
338 338 'in2_template', 'out_template'])
339 339
340 340 def update_prompt(self, name, new_template=None):
341 341 """This is called when a prompt template is updated. It processes
342 342 abbreviations used in the prompt template (like \#) and calculates how
343 343 many invisible characters (ANSI colour escapes) the resulting prompt
344 344 contains.
345 345
346 346 It is also called for each prompt on changing the colour scheme. In both
347 347 cases, traitlets should take care of calling this automatically.
348 348 """
349 349 if new_template is not None:
350 350 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
351 351 # We count invisible characters (colour escapes) on the last line of the
352 352 # prompt, to calculate the width for lining up subsequent prompts.
353 353 invis_chars = _lenlastline(self._render(name, color=True)) - \
354 354 _lenlastline(self._render(name, color=False))
355 355 self.invisible_chars[name] = invis_chars
356 356
357 357 def _update_prompt_trait(self, traitname, new_template):
358 358 name = traitname[:-9] # Cut off '_template'
359 359 self.update_prompt(name, new_template)
360 360
361 361 def _render(self, name, color=True, **kwargs):
362 362 """Render but don't justify, or update the width or txtwidth attributes.
363 363 """
364 364 if name == 'rewrite':
365 365 return self._render_rewrite(color=color)
366 366
367 367 if color:
368 368 scheme = self.color_scheme_table.active_colors
369 369 if name=='out':
370 370 colors = color_lists['normal']
371 371 colors.number, colors.prompt, colors.normal = \
372 372 scheme.out_number, scheme.out_prompt, scheme.normal
373 373 else:
374 374 colors = color_lists['inp']
375 375 colors.number, colors.prompt, colors.normal = \
376 376 scheme.in_number, scheme.in_prompt, scheme.in_normal
377 377 if name=='in2':
378 378 colors.prompt = scheme.in_prompt2
379 379 else:
380 380 # No color
381 381 colors = color_lists['nocolor']
382 382 colors.number, colors.prompt, colors.normal = '', '', ''
383 383
384 384 count = self.shell.execution_count # Shorthand
385 385 # Build the dictionary to be passed to string formatting
386 386 fmtargs = dict(color=colors, count=count,
387 387 dots="."*len(str(count)),
388 388 width=self.width, txtwidth=self.txtwidth )
389 389 fmtargs.update(self.lazy_evaluate_fields)
390 390 fmtargs.update(kwargs)
391 391
392 392 # Prepare the prompt
393 393 prompt = colors.prompt + self.templates[name] + colors.normal
394 394
395 395 # Fill in required fields
396 396 return self._formatter.format(prompt, **fmtargs)
397 397
398 398 def _render_rewrite(self, color=True):
399 399 """Render the ---> rewrite prompt."""
400 400 if color:
401 401 scheme = self.color_scheme_table.active_colors
402 402 # We need a non-input version of these escapes
403 403 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
404 404 color_normal = scheme.normal
405 405 else:
406 406 color_prompt, color_normal = '', ''
407 407
408 408 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
409 409
410 410 def render(self, name, color=True, just=None, **kwargs):
411 411 """
412 412 Render the selected prompt.
413 413
414 414 Parameters
415 415 ----------
416 416 name : str
417 417 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
418 418 color : bool
419 419 If True (default), include ANSI escape sequences for a coloured prompt.
420 420 just : bool
421 421 If True, justify the prompt to the width of the last prompt. The
422 422 default is stored in self.justify.
423 423 **kwargs :
424 424 Additional arguments will be passed to the string formatting operation,
425 425 so they can override the values that would otherwise fill in the
426 426 template.
427 427
428 428 Returns
429 429 -------
430 430 A string containing the rendered prompt.
431 431 """
432 432 res = self._render(name, color=color, **kwargs)
433 433
434 434 # Handle justification of prompt
435 435 invis_chars = self.invisible_chars[name] if color else 0
436 436 self.txtwidth = _lenlastline(res) - invis_chars
437 437 just = self.justify if (just is None) else just
438 438 # If the prompt spans more than one line, don't try to justify it:
439 439 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
440 440 res = res.rjust(self.width + invis_chars)
441 441 self.width = _lenlastline(res) - invis_chars
442 442 return res
@@ -1,432 +1,433 b''
1 1 # encoding: utf-8
2 2 """
3 3 A mixin for :class:`~IPython.core.application.Application` classes that
4 4 launch InteractiveShell instances, load extensions, etc.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 from __future__ import absolute_import
11 11 from __future__ import print_function
12 12
13 13 import glob
14 14 import os
15 15 import sys
16 16
17 17 from IPython.config.application import boolean_flag
18 18 from IPython.config.configurable import Configurable
19 19 from IPython.config.loader import Config
20 20 from IPython.core import pylabtools
21 21 from IPython.utils import py3compat
22 22 from IPython.utils.contexts import preserve_keys
23 23 from IPython.utils.path import filefind
24 24 from IPython.utils.traitlets import (
25 25 Unicode, Instance, List, Bool, CaselessStrEnum
26 26 )
27 27 from IPython.lib.inputhook import guis
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Aliases and Flags
31 31 #-----------------------------------------------------------------------------
32 32
33 33 gui_keys = tuple(sorted([ key for key in guis if key is not None ]))
34 34
35 35 backend_keys = sorted(pylabtools.backends.keys())
36 36 backend_keys.insert(0, 'auto')
37 37
38 38 shell_flags = {}
39 39
40 40 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
41 41 addflag('autoindent', 'InteractiveShell.autoindent',
42 42 'Turn on autoindenting.', 'Turn off autoindenting.'
43 43 )
44 44 addflag('automagic', 'InteractiveShell.automagic',
45 45 """Turn on the auto calling of magic commands. Type %%magic at the
46 46 IPython prompt for more information.""",
47 47 'Turn off the auto calling of magic commands.'
48 48 )
49 49 addflag('pdb', 'InteractiveShell.pdb',
50 50 "Enable auto calling the pdb debugger after every exception.",
51 51 "Disable auto calling the pdb debugger after every exception."
52 52 )
53 53 # pydb flag doesn't do any config, as core.debugger switches on import,
54 54 # which is before parsing. This just allows the flag to be passed.
55 55 shell_flags.update(dict(
56 56 pydb = ({},
57 57 """Use the third party 'pydb' package as debugger, instead of pdb.
58 58 Requires that pydb is installed."""
59 59 )
60 60 ))
61 61 addflag('pprint', 'PlainTextFormatter.pprint',
62 62 "Enable auto pretty printing of results.",
63 63 "Disable auto pretty printing of results."
64 64 )
65 65 addflag('color-info', 'InteractiveShell.color_info',
66 66 """IPython can display information about objects via a set of functions,
67 67 and optionally can use colors for this, syntax highlighting
68 68 source code and various other elements. This is on by default, but can cause
69 69 problems with some pagers. If you see such problems, you can disable the
70 70 colours.""",
71 71 "Disable using colors for info related things."
72 72 )
73 73 addflag('deep-reload', 'InteractiveShell.deep_reload',
74 74 """Enable deep (recursive) reloading by default. IPython can use the
75 75 deep_reload module which reloads changes in modules recursively (it
76 76 replaces the reload() function, so you don't need to change anything to
77 77 use it). deep_reload() forces a full reload of modules whose code may
78 78 have changed, which the default reload() function does not. When
79 79 deep_reload is off, IPython will use the normal reload(), but
80 80 deep_reload will still be available as dreload(). This feature is off
81 81 by default [which means that you have both normal reload() and
82 82 dreload()].""",
83 83 "Disable deep (recursive) reloading by default."
84 84 )
85 85 nosep_config = Config()
86 86 nosep_config.InteractiveShell.separate_in = ''
87 87 nosep_config.InteractiveShell.separate_out = ''
88 88 nosep_config.InteractiveShell.separate_out2 = ''
89 89
90 90 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
91 91 shell_flags['pylab'] = (
92 92 {'InteractiveShellApp' : {'pylab' : 'auto'}},
93 93 """Pre-load matplotlib and numpy for interactive use with
94 94 the default matplotlib backend."""
95 95 )
96 96 shell_flags['matplotlib'] = (
97 97 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
98 98 """Configure matplotlib for interactive use with
99 99 the default matplotlib backend."""
100 100 )
101 101
102 102 # it's possible we don't want short aliases for *all* of these:
103 103 shell_aliases = dict(
104 104 autocall='InteractiveShell.autocall',
105 105 colors='InteractiveShell.colors',
106 106 logfile='InteractiveShell.logfile',
107 107 logappend='InteractiveShell.logappend',
108 108 c='InteractiveShellApp.code_to_run',
109 109 m='InteractiveShellApp.module_to_run',
110 110 ext='InteractiveShellApp.extra_extension',
111 111 gui='InteractiveShellApp.gui',
112 112 pylab='InteractiveShellApp.pylab',
113 113 matplotlib='InteractiveShellApp.matplotlib',
114 114 )
115 115 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
116 116
117 117 #-----------------------------------------------------------------------------
118 118 # Main classes and functions
119 119 #-----------------------------------------------------------------------------
120 120
121 121 class InteractiveShellApp(Configurable):
122 122 """A Mixin for applications that start InteractiveShell instances.
123 123
124 124 Provides configurables for loading extensions and executing files
125 125 as part of configuring a Shell environment.
126 126
127 127 The following methods should be called by the :meth:`initialize` method
128 128 of the subclass:
129 129
130 130 - :meth:`init_path`
131 131 - :meth:`init_shell` (to be implemented by the subclass)
132 132 - :meth:`init_gui_pylab`
133 133 - :meth:`init_extensions`
134 134 - :meth:`init_code`
135 135 """
136 136 extensions = List(Unicode, config=True,
137 137 help="A list of dotted module names of IPython extensions to load."
138 138 )
139 139 extra_extension = Unicode('', config=True,
140 140 help="dotted module name of an IPython extension to load."
141 141 )
142 142
143 143 reraise_ipython_extension_failures = Bool(
144 144 False,
145 145 config=True,
146 146 help="Reraise exceptions encountered loading IPython extensions?",
147 147 )
148 148
149 149 # Extensions that are always loaded (not configurable)
150 150 default_extensions = List(Unicode, [u'storemagic'], config=False)
151 151
152 152 hide_initial_ns = Bool(True, config=True,
153 153 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
154 154 be hidden from tools like %who?"""
155 155 )
156 156
157 157 exec_files = List(Unicode, config=True,
158 158 help="""List of files to run at IPython startup."""
159 159 )
160 160 exec_PYTHONSTARTUP = Bool(True, config=True,
161 161 help="""Run the file referenced by the PYTHONSTARTUP environment
162 162 variable at IPython startup."""
163 163 )
164 164 file_to_run = Unicode('', config=True,
165 165 help="""A file to be run""")
166 166
167 167 exec_lines = List(Unicode, config=True,
168 168 help="""lines of code to run at IPython startup."""
169 169 )
170 170 code_to_run = Unicode('', config=True,
171 171 help="Execute the given command string."
172 172 )
173 173 module_to_run = Unicode('', config=True,
174 174 help="Run the module as a script."
175 175 )
176 176 gui = CaselessStrEnum(gui_keys, config=True, allow_none=True,
177 177 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
178 178 )
179 179 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
180 180 config=True,
181 181 help="""Configure matplotlib for interactive use with
182 182 the default matplotlib backend."""
183 183 )
184 184 pylab = CaselessStrEnum(backend_keys, allow_none=True,
185 185 config=True,
186 186 help="""Pre-load matplotlib and numpy for interactive use,
187 187 selecting a particular matplotlib backend and loop integration.
188 188 """
189 189 )
190 190 pylab_import_all = Bool(True, config=True,
191 191 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
192 192 and an ``import *`` is done from numpy and pylab, when using pylab mode.
193 193
194 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 200 user_ns = Instance(dict, args=None, allow_none=True)
200 201 def _user_ns_changed(self, name, old, new):
201 202 if self.shell is not None:
202 203 self.shell.user_ns = new
203 204 self.shell.init_user_ns()
204 205
205 206 def init_path(self):
206 207 """Add current working directory, '', to sys.path"""
207 208 if sys.path[0] != '':
208 209 sys.path.insert(0, '')
209 210
210 211 def init_shell(self):
211 212 raise NotImplementedError("Override in subclasses")
212 213
213 214 def init_gui_pylab(self):
214 215 """Enable GUI event loop integration, taking pylab into account."""
215 216 enable = False
216 217 shell = self.shell
217 218 if self.pylab:
218 219 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
219 220 key = self.pylab
220 221 elif self.matplotlib:
221 222 enable = shell.enable_matplotlib
222 223 key = self.matplotlib
223 224 elif self.gui:
224 225 enable = shell.enable_gui
225 226 key = self.gui
226 227
227 228 if not enable:
228 229 return
229 230
230 231 try:
231 232 r = enable(key)
232 233 except ImportError:
233 234 self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?")
234 235 self.shell.showtraceback()
235 236 return
236 237 except Exception:
237 238 self.log.warn("GUI event loop or pylab initialization failed")
238 239 self.shell.showtraceback()
239 240 return
240 241
241 242 if isinstance(r, tuple):
242 243 gui, backend = r[:2]
243 244 self.log.info("Enabling GUI event loop integration, "
244 245 "eventloop=%s, matplotlib=%s", gui, backend)
245 246 if key == "auto":
246 247 print("Using matplotlib backend: %s" % backend)
247 248 else:
248 249 gui = r
249 250 self.log.info("Enabling GUI event loop integration, "
250 251 "eventloop=%s", gui)
251 252
252 253 def init_extensions(self):
253 254 """Load all IPython extensions in IPythonApp.extensions.
254 255
255 256 This uses the :meth:`ExtensionManager.load_extensions` to load all
256 257 the extensions listed in ``self.extensions``.
257 258 """
258 259 try:
259 260 self.log.debug("Loading IPython extensions...")
260 261 extensions = self.default_extensions + self.extensions
261 262 if self.extra_extension:
262 263 extensions.append(self.extra_extension)
263 264 for ext in extensions:
264 265 try:
265 266 self.log.info("Loading IPython extension: %s" % ext)
266 267 self.shell.extension_manager.load_extension(ext)
267 268 except:
268 269 if self.reraise_ipython_extension_failures:
269 270 raise
270 271 msg = ("Error in loading extension: {ext}\n"
271 272 "Check your config files in {location}".format(
272 273 ext=ext,
273 274 location=self.profile_dir.location
274 275 ))
275 276 self.log.warn(msg, exc_info=True)
276 277 except:
277 278 if self.reraise_ipython_extension_failures:
278 279 raise
279 280 self.log.warn("Unknown error in loading extensions:", exc_info=True)
280 281
281 282 def init_code(self):
282 283 """run the pre-flight code, specified via exec_lines"""
283 284 self._run_startup_files()
284 285 self._run_exec_lines()
285 286 self._run_exec_files()
286 287
287 288 # Hide variables defined here from %who etc.
288 289 if self.hide_initial_ns:
289 290 self.shell.user_ns_hidden.update(self.shell.user_ns)
290 291
291 292 # command-line execution (ipython -i script.py, ipython -m module)
292 293 # should *not* be excluded from %whos
293 294 self._run_cmd_line_code()
294 295 self._run_module()
295 296
296 297 # flush output, so itwon't be attached to the first cell
297 298 sys.stdout.flush()
298 299 sys.stderr.flush()
299 300
300 301 def _run_exec_lines(self):
301 302 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
302 303 if not self.exec_lines:
303 304 return
304 305 try:
305 306 self.log.debug("Running code from IPythonApp.exec_lines...")
306 307 for line in self.exec_lines:
307 308 try:
308 309 self.log.info("Running code in user namespace: %s" %
309 310 line)
310 311 self.shell.run_cell(line, store_history=False)
311 312 except:
312 313 self.log.warn("Error in executing line in user "
313 314 "namespace: %s" % line)
314 315 self.shell.showtraceback()
315 316 except:
316 317 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
317 318 self.shell.showtraceback()
318 319
319 320 def _exec_file(self, fname, shell_futures=False):
320 321 try:
321 322 full_filename = filefind(fname, [u'.', self.ipython_dir])
322 323 except IOError as e:
323 324 self.log.warn("File not found: %r"%fname)
324 325 return
325 326 # Make sure that the running script gets a proper sys.argv as if it
326 327 # were run from a system shell.
327 328 save_argv = sys.argv
328 329 sys.argv = [full_filename] + self.extra_args[1:]
329 330 # protect sys.argv from potential unicode strings on Python 2:
330 331 if not py3compat.PY3:
331 332 sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ]
332 333 try:
333 334 if os.path.isfile(full_filename):
334 335 self.log.info("Running file in user namespace: %s" %
335 336 full_filename)
336 337 # Ensure that __file__ is always defined to match Python
337 338 # behavior.
338 339 with preserve_keys(self.shell.user_ns, '__file__'):
339 340 self.shell.user_ns['__file__'] = fname
340 341 if full_filename.endswith('.ipy'):
341 342 self.shell.safe_execfile_ipy(full_filename,
342 343 shell_futures=shell_futures)
343 344 else:
344 345 # default to python, even without extension
345 346 self.shell.safe_execfile(full_filename,
346 347 self.shell.user_ns,
347 348 shell_futures=shell_futures)
348 349 finally:
349 350 sys.argv = save_argv
350 351
351 352 def _run_startup_files(self):
352 353 """Run files from profile startup directory"""
353 354 startup_dir = self.profile_dir.startup_dir
354 355 startup_files = []
355 356
356 357 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
357 358 not (self.file_to_run or self.code_to_run or self.module_to_run):
358 359 python_startup = os.environ['PYTHONSTARTUP']
359 360 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
360 361 try:
361 362 self._exec_file(python_startup)
362 363 except:
363 364 self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
364 365 self.shell.showtraceback()
365 366 finally:
366 367 # Many PYTHONSTARTUP files set up the readline completions,
367 368 # but this is often at odds with IPython's own completions.
368 369 # Do not allow PYTHONSTARTUP to set up readline.
369 370 if self.shell.has_readline:
370 371 self.shell.set_readline_completer()
371 372
372 373 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
373 374 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
374 375 if not startup_files:
375 376 return
376 377
377 378 self.log.debug("Running startup files from %s...", startup_dir)
378 379 try:
379 380 for fname in sorted(startup_files):
380 381 self._exec_file(fname)
381 382 except:
382 383 self.log.warn("Unknown error in handling startup files:")
383 384 self.shell.showtraceback()
384 385
385 386 def _run_exec_files(self):
386 387 """Run files from IPythonApp.exec_files"""
387 388 if not self.exec_files:
388 389 return
389 390
390 391 self.log.debug("Running files in IPythonApp.exec_files...")
391 392 try:
392 393 for fname in self.exec_files:
393 394 self._exec_file(fname)
394 395 except:
395 396 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
396 397 self.shell.showtraceback()
397 398
398 399 def _run_cmd_line_code(self):
399 400 """Run code or file specified at the command-line"""
400 401 if self.code_to_run:
401 402 line = self.code_to_run
402 403 try:
403 404 self.log.info("Running code given at command line (c=): %s" %
404 405 line)
405 406 self.shell.run_cell(line, store_history=False)
406 407 except:
407 408 self.log.warn("Error in executing line in user namespace: %s" %
408 409 line)
409 410 self.shell.showtraceback()
410 411
411 412 # Like Python itself, ignore the second if the first of these is present
412 413 elif self.file_to_run:
413 414 fname = self.file_to_run
414 415 try:
415 416 self._exec_file(fname, shell_futures=True)
416 417 except:
417 418 self.log.warn("Error in executing file in user namespace: %s" %
418 419 fname)
419 420 self.shell.showtraceback()
420 421
421 422 def _run_module(self):
422 423 """Run module specified at the command-line."""
423 424 if self.module_to_run:
424 425 # Make sure that the module gets a proper sys.argv as if it were
425 426 # run using `python -m`.
426 427 save_argv = sys.argv
427 428 sys.argv = [sys.executable] + self.extra_args
428 429 try:
429 430 self.shell.safe_run_module(self.module_to_run,
430 431 self.shell.user_ns)
431 432 finally:
432 433 sys.argv = save_argv
@@ -1,1127 +1,1127 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import datetime
11 11 import errno
12 12 import importlib
13 13 import io
14 14 import json
15 15 import logging
16 16 import os
17 17 import random
18 18 import re
19 19 import select
20 20 import signal
21 21 import socket
22 22 import sys
23 23 import threading
24 24 import webbrowser
25 25
26 26
27 27 # check for pyzmq
28 28 from IPython.utils.zmqrelated import check_for_zmq
29 29 check_for_zmq('13', 'IPython.html')
30 30
31 31 from jinja2 import Environment, FileSystemLoader
32 32
33 33 # Install the pyzmq ioloop. This has to be done before anything else from
34 34 # tornado is imported.
35 35 from zmq.eventloop import ioloop
36 36 ioloop.install()
37 37
38 38 # check for tornado 3.1.0
39 39 msg = "The IPython Notebook requires tornado >= 4.0"
40 40 try:
41 41 import tornado
42 42 except ImportError:
43 43 raise ImportError(msg)
44 44 try:
45 45 version_info = tornado.version_info
46 46 except AttributeError:
47 47 raise ImportError(msg + ", but you have < 1.1.0")
48 48 if version_info < (4,0):
49 49 raise ImportError(msg + ", but you have %s" % tornado.version)
50 50
51 51 from tornado import httpserver
52 52 from tornado import web
53 53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54 54
55 55 from IPython.html import (
56 56 DEFAULT_STATIC_FILES_PATH,
57 57 DEFAULT_TEMPLATE_PATH_LIST,
58 58 )
59 59 from .base.handlers import Template404
60 60 from .log import log_request
61 61 from .services.kernels.kernelmanager import MappingKernelManager
62 62 from .services.config import ConfigManager
63 63 from .services.contents.manager import ContentsManager
64 64 from .services.contents.filemanager import FileContentsManager
65 65 from .services.clusters.clustermanager import ClusterManager
66 66 from .services.sessions.sessionmanager import SessionManager
67 67
68 68 from .auth.login import LoginHandler
69 69 from .auth.logout import LogoutHandler
70 70 from .base.handlers import IPythonHandler, FileFindHandler
71 71
72 72 from IPython.config import Config
73 73 from IPython.config.application import catch_config_error, boolean_flag
74 74 from IPython.core.application import (
75 75 BaseIPythonApplication, base_flags, base_aliases,
76 76 )
77 77 from IPython.core.profiledir import ProfileDir
78 78 from IPython.kernel import KernelManager
79 79 from IPython.kernel.kernelspec import KernelSpecManager
80 80 from IPython.kernel.zmq.session import Session
81 81 from IPython.nbformat.sign import NotebookNotary
82 82 from IPython.utils.importstring import import_item
83 83 from IPython.utils import submodule
84 84 from IPython.utils.process import check_pid
85 85 from IPython.utils.traitlets import (
86 86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 87 TraitError, Type,
88 88 )
89 89 from IPython.utils import py3compat
90 90 from IPython.utils.path import filefind, get_ipython_dir
91 91 from IPython.utils.sysinfo import get_sys_info
92 92
93 93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 94 from .utils import url_path_join
95 95
96 96 #-----------------------------------------------------------------------------
97 97 # Module globals
98 98 #-----------------------------------------------------------------------------
99 99
100 100 _examples = """
101 101 ipython notebook # start the notebook
102 102 ipython notebook --profile=sympy # use the sympy profile
103 103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 104 """
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Helper functions
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def random_ports(port, n):
111 111 """Generate a list of n random ports near the given port.
112 112
113 113 The first 5 ports will be sequential, and the remaining n-5 will be
114 114 randomly selected in the range [port-2*n, port+2*n].
115 115 """
116 116 for i in range(min(5, n)):
117 117 yield port + i
118 118 for i in range(n-5):
119 119 yield max(1, port + random.randint(-2*n, 2*n))
120 120
121 121 def load_handlers(name):
122 122 """Load the (URL pattern, handler) tuples for each component."""
123 123 name = 'IPython.html.' + name
124 124 mod = __import__(name, fromlist=['default_handlers'])
125 125 return mod.default_handlers
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # The Tornado web application
129 129 #-----------------------------------------------------------------------------
130 130
131 131 class NotebookWebApplication(web.Application):
132 132
133 133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 134 cluster_manager, session_manager, kernel_spec_manager,
135 135 config_manager, log,
136 136 base_url, default_url, settings_overrides, jinja_env_options):
137 137
138 138 settings = self.init_settings(
139 139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 141 default_url, settings_overrides, jinja_env_options)
142 142 handlers = self.init_handlers(settings)
143 143
144 144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 145
146 146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 147 cluster_manager, session_manager, kernel_spec_manager,
148 148 config_manager,
149 149 log, base_url, default_url, settings_overrides,
150 150 jinja_env_options=None):
151 151
152 152 _template_path = settings_overrides.get(
153 153 "template_path",
154 154 ipython_app.template_file_path,
155 155 )
156 156 if isinstance(_template_path, str):
157 157 _template_path = (_template_path,)
158 158 template_path = [os.path.expanduser(path) for path in _template_path]
159 159
160 160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162 162
163 163 sys_info = get_sys_info()
164 164 if sys_info['commit_source'] == 'repository':
165 165 # don't cache (rely on 304) when working from master
166 166 version_hash = ''
167 167 else:
168 168 # reset the cache on server restart
169 169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170 170
171 171 settings = dict(
172 172 # basics
173 173 log_function=log_request,
174 174 base_url=base_url,
175 175 default_url=default_url,
176 176 template_path=template_path,
177 177 static_path=ipython_app.static_file_path,
178 178 static_handler_class = FileFindHandler,
179 179 static_url_prefix = url_path_join(base_url,'/static/'),
180 180 static_handler_args = {
181 181 # don't cache custom.js
182 182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 183 },
184 184 version_hash=version_hash,
185 185
186 186 # authentication
187 187 cookie_secret=ipython_app.cookie_secret,
188 188 login_url=url_path_join(base_url,'/login'),
189 189 login_handler_class=ipython_app.login_handler_class,
190 190 logout_handler_class=ipython_app.logout_handler_class,
191 191 password=ipython_app.password,
192 192
193 193 # managers
194 194 kernel_manager=kernel_manager,
195 195 contents_manager=contents_manager,
196 196 cluster_manager=cluster_manager,
197 197 session_manager=session_manager,
198 198 kernel_spec_manager=kernel_spec_manager,
199 199 config_manager=config_manager,
200 200
201 201 # IPython stuff
202 202 nbextensions_path=ipython_app.nbextensions_path,
203 203 websocket_url=ipython_app.websocket_url,
204 204 mathjax_url=ipython_app.mathjax_url,
205 205 config=ipython_app.config,
206 206 jinja2_env=env,
207 207 terminals_available=False, # Set later if terminals are available
208 208 )
209 209
210 210 # allow custom overrides for the tornado web app.
211 211 settings.update(settings_overrides)
212 212 return settings
213 213
214 214 def init_handlers(self, settings):
215 215 """Load the (URL pattern, handler) tuples for each component."""
216 216
217 217 # Order matters. The first handler to match the URL will handle the request.
218 218 handlers = []
219 219 handlers.extend(load_handlers('tree.handlers'))
220 220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 222 handlers.extend(load_handlers('files.handlers'))
223 223 handlers.extend(load_handlers('notebook.handlers'))
224 224 handlers.extend(load_handlers('nbconvert.handlers'))
225 225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 226 handlers.extend(load_handlers('edit.handlers'))
227 227 handlers.extend(load_handlers('services.config.handlers'))
228 228 handlers.extend(load_handlers('services.kernels.handlers'))
229 229 handlers.extend(load_handlers('services.contents.handlers'))
230 230 handlers.extend(load_handlers('services.clusters.handlers'))
231 231 handlers.extend(load_handlers('services.sessions.handlers'))
232 232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 234 handlers.extend(load_handlers('services.security.handlers'))
235 235 handlers.append(
236 236 (r"/nbextensions/(.*)", FileFindHandler, {
237 237 'path': settings['nbextensions_path'],
238 238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 239 }),
240 240 )
241 241 # register base handlers last
242 242 handlers.extend(load_handlers('base.handlers'))
243 243 # set the URL that will be redirected from `/`
244 244 handlers.append(
245 245 (r'/?', web.RedirectHandler, {
246 246 'url' : settings['default_url'],
247 247 'permanent': False, # want 302, not 301
248 248 })
249 249 )
250 250 # prepend base_url onto the patterns that we match
251 251 new_handlers = []
252 252 for handler in handlers:
253 253 pattern = url_path_join(settings['base_url'], handler[0])
254 254 new_handler = tuple([pattern] + list(handler[1:]))
255 255 new_handlers.append(new_handler)
256 256 # add 404 on the end, which will catch everything that falls through
257 257 new_handlers.append((r'(.*)', Template404))
258 258 return new_handlers
259 259
260 260
261 261 class NbserverListApp(BaseIPythonApplication):
262 262
263 263 description="List currently running notebook servers in this profile."
264 264
265 265 flags = dict(
266 266 json=({'NbserverListApp': {'json': True}},
267 267 "Produce machine-readable JSON output."),
268 268 )
269 269
270 270 json = Bool(False, config=True,
271 271 help="If True, each line of output will be a JSON object with the "
272 272 "details from the server info file.")
273 273
274 274 def start(self):
275 275 if not self.json:
276 276 print("Currently running servers:")
277 277 for serverinfo in list_running_servers(self.profile):
278 278 if self.json:
279 279 print(json.dumps(serverinfo))
280 280 else:
281 281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282 282
283 283 #-----------------------------------------------------------------------------
284 284 # Aliases and Flags
285 285 #-----------------------------------------------------------------------------
286 286
287 287 flags = dict(base_flags)
288 288 flags['no-browser']=(
289 289 {'NotebookApp' : {'open_browser' : False}},
290 290 "Don't open the notebook in a browser after startup."
291 291 )
292 292 flags['pylab']=(
293 293 {'NotebookApp' : {'pylab' : 'warn'}},
294 294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 295 )
296 296 flags['no-mathjax']=(
297 297 {'NotebookApp' : {'enable_mathjax' : False}},
298 298 """Disable MathJax
299 299
300 300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 301 very large, so you may want to disable it if you have a slow internet
302 302 connection, or for offline use of the notebook.
303 303
304 304 When disabled, equations etc. will appear as their untransformed TeX source.
305 305 """
306 306 )
307 307
308 308 # Add notebook manager flags
309 309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 310 'DEPRECATED, IGNORED',
311 311 'DEPRECATED, IGNORED'))
312 312
313 313 aliases = dict(base_aliases)
314 314
315 315 aliases.update({
316 316 'ip': 'NotebookApp.ip',
317 317 'port': 'NotebookApp.port',
318 318 'port-retries': 'NotebookApp.port_retries',
319 319 'transport': 'KernelManager.transport',
320 320 'keyfile': 'NotebookApp.keyfile',
321 321 'certfile': 'NotebookApp.certfile',
322 322 'notebook-dir': 'NotebookApp.notebook_dir',
323 323 'browser': 'NotebookApp.browser',
324 324 'pylab': 'NotebookApp.pylab',
325 325 })
326 326
327 327 #-----------------------------------------------------------------------------
328 328 # NotebookApp
329 329 #-----------------------------------------------------------------------------
330 330
331 331 class NotebookApp(BaseIPythonApplication):
332 332
333 333 name = 'ipython-notebook'
334 334
335 335 description = """
336 336 The IPython HTML Notebook.
337 337
338 338 This launches a Tornado based HTML Notebook Server that serves up an
339 339 HTML5/Javascript Notebook client.
340 340 """
341 341 examples = _examples
342 342 aliases = aliases
343 343 flags = flags
344 344
345 345 classes = [
346 346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 347 ContentsManager, FileContentsManager, NotebookNotary,
348 348 KernelSpecManager,
349 349 ]
350 350 flags = Dict(flags)
351 351 aliases = Dict(aliases)
352 352
353 353 subcommands = dict(
354 354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 355 )
356 356
357 357 _log_formatter_cls = LogFormatter
358 358
359 359 def _log_level_default(self):
360 360 return logging.INFO
361 361
362 362 def _log_datefmt_default(self):
363 363 """Exclude date from default date format"""
364 364 return "%H:%M:%S"
365 365
366 366 def _log_format_default(self):
367 367 """override default log format to include time"""
368 368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369 369
370 370 # create requested profiles by default, if they don't exist:
371 371 auto_create = Bool(True)
372 372
373 373 # file to be opened in the notebook server
374 374 file_to_run = Unicode('', config=True)
375 375
376 376 # Network related information
377 377
378 378 allow_origin = Unicode('', config=True,
379 379 help="""Set the Access-Control-Allow-Origin header
380 380
381 381 Use '*' to allow any origin to access your server.
382 382
383 383 Takes precedence over allow_origin_pat.
384 384 """
385 385 )
386 386
387 387 allow_origin_pat = Unicode('', config=True,
388 388 help="""Use a regular expression for the Access-Control-Allow-Origin header
389 389
390 390 Requests from an origin matching the expression will get replies with:
391 391
392 392 Access-Control-Allow-Origin: origin
393 393
394 394 where `origin` is the origin of the request.
395 395
396 396 Ignored if allow_origin is set.
397 397 """
398 398 )
399 399
400 400 allow_credentials = Bool(False, config=True,
401 401 help="Set the Access-Control-Allow-Credentials: true header"
402 402 )
403 403
404 404 default_url = Unicode('/tree', config=True,
405 405 help="The default URL to redirect to from `/`"
406 406 )
407 407
408 408 ip = Unicode('localhost', config=True,
409 409 help="The IP address the notebook server will listen on."
410 410 )
411 411 def _ip_default(self):
412 412 """Return localhost if available, 127.0.0.1 otherwise.
413 413
414 414 On some (horribly broken) systems, localhost cannot be bound.
415 415 """
416 416 s = socket.socket()
417 417 try:
418 418 s.bind(('localhost', 0))
419 419 except socket.error as e:
420 420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
421 421 return '127.0.0.1'
422 422 else:
423 423 s.close()
424 424 return 'localhost'
425 425
426 426 def _ip_changed(self, name, old, new):
427 427 if new == u'*': self.ip = u''
428 428
429 429 port = Integer(8888, config=True,
430 430 help="The port the notebook server will listen on."
431 431 )
432 432 port_retries = Integer(50, config=True,
433 433 help="The number of additional ports to try if the specified port is not available."
434 434 )
435 435
436 436 certfile = Unicode(u'', config=True,
437 437 help="""The full path to an SSL/TLS certificate file."""
438 438 )
439 439
440 440 keyfile = Unicode(u'', config=True,
441 441 help="""The full path to a private key file for usage with SSL/TLS."""
442 442 )
443 443
444 444 cookie_secret_file = Unicode(config=True,
445 445 help="""The file where the cookie secret is stored."""
446 446 )
447 447 def _cookie_secret_file_default(self):
448 448 if self.profile_dir is None:
449 449 return ''
450 450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
451 451
452 452 cookie_secret = Bytes(b'', config=True,
453 453 help="""The random bytes used to secure cookies.
454 454 By default this is a new random number every time you start the Notebook.
455 455 Set it to a value in a config file to enable logins to persist across server sessions.
456 456
457 457 Note: Cookie secrets should be kept private, do not share config files with
458 458 cookie_secret stored in plaintext (you can read the value from a file).
459 459 """
460 460 )
461 461 def _cookie_secret_default(self):
462 462 if os.path.exists(self.cookie_secret_file):
463 463 with io.open(self.cookie_secret_file, 'rb') as f:
464 464 return f.read()
465 465 else:
466 466 secret = base64.encodestring(os.urandom(1024))
467 467 self._write_cookie_secret_file(secret)
468 468 return secret
469 469
470 470 def _write_cookie_secret_file(self, secret):
471 471 """write my secret to my secret_file"""
472 472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
473 473 with io.open(self.cookie_secret_file, 'wb') as f:
474 474 f.write(secret)
475 475 try:
476 476 os.chmod(self.cookie_secret_file, 0o600)
477 477 except OSError:
478 478 self.log.warn(
479 479 "Could not set permissions on %s",
480 480 self.cookie_secret_file
481 481 )
482 482
483 483 password = Unicode(u'', config=True,
484 484 help="""Hashed password to use for web authentication.
485 485
486 486 To generate, type in a python/IPython shell:
487 487
488 488 from IPython.lib import passwd; passwd()
489 489
490 490 The string should be of the form type:salt:hashed-password.
491 491 """
492 492 )
493 493
494 494 open_browser = Bool(True, config=True,
495 495 help="""Whether to open in a browser after starting.
496 496 The specific browser used is platform dependent and
497 497 determined by the python standard library `webbrowser`
498 498 module, unless it is overridden using the --browser
499 499 (NotebookApp.browser) configuration option.
500 500 """)
501 501
502 502 browser = Unicode(u'', config=True,
503 503 help="""Specify what command to use to invoke a web
504 504 browser when opening the notebook. If not specified, the
505 505 default browser will be determined by the `webbrowser`
506 506 standard library module, which allows setting of the
507 507 BROWSER environment variable to override it.
508 508 """)
509 509
510 510 webapp_settings = Dict(config=True,
511 511 help="DEPRECATED, use tornado_settings"
512 512 )
513 513 def _webapp_settings_changed(self, name, old, new):
514 514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
515 515 self.tornado_settings = new
516 516
517 517 tornado_settings = Dict(config=True,
518 518 help="Supply overrides for the tornado.web.Application that the "
519 519 "IPython notebook uses.")
520 520
521 521 ssl_options = Dict(config=True,
522 522 help="""Supply SSL options for the tornado HTTPServer.
523 523 See the tornado docs for details.""")
524 524
525 525 jinja_environment_options = Dict(config=True,
526 526 help="Supply extra arguments that will be passed to Jinja environment.")
527 527
528 528 enable_mathjax = Bool(True, config=True,
529 529 help="""Whether to enable MathJax for typesetting math/TeX
530 530
531 531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
532 532 very large, so you may want to disable it if you have a slow internet
533 533 connection, or for offline use of the notebook.
534 534
535 535 When disabled, equations etc. will appear as their untransformed TeX source.
536 536 """
537 537 )
538 538 def _enable_mathjax_changed(self, name, old, new):
539 539 """set mathjax url to empty if mathjax is disabled"""
540 540 if not new:
541 541 self.mathjax_url = u''
542 542
543 543 base_url = Unicode('/', config=True,
544 544 help='''The base URL for the notebook server.
545 545
546 546 Leading and trailing slashes can be omitted,
547 547 and will automatically be added.
548 548 ''')
549 549 def _base_url_changed(self, name, old, new):
550 550 if not new.startswith('/'):
551 551 self.base_url = '/'+new
552 552 elif not new.endswith('/'):
553 553 self.base_url = new+'/'
554 554
555 555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
556 556 def _base_project_url_changed(self, name, old, new):
557 557 self.log.warn("base_project_url is deprecated, use base_url")
558 558 self.base_url = new
559 559
560 560 extra_static_paths = List(Unicode, config=True,
561 561 help="""Extra paths to search for serving static files.
562 562
563 563 This allows adding javascript/css to be available from the notebook server machine,
564 564 or overriding individual files in the IPython"""
565 565 )
566 566 def _extra_static_paths_default(self):
567 567 return [os.path.join(self.profile_dir.location, 'static')]
568 568
569 569 @property
570 570 def static_file_path(self):
571 571 """return extra paths + the default location"""
572 572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
573 573
574 574 extra_template_paths = List(Unicode, config=True,
575 575 help="""Extra paths to search for serving jinja templates.
576 576
577 577 Can be used to override templates from IPython.html.templates."""
578 578 )
579 579 def _extra_template_paths_default(self):
580 580 return []
581 581
582 582 @property
583 583 def template_file_path(self):
584 584 """return extra paths + the default locations"""
585 585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
586 586
587 587 extra_nbextensions_path = List(Unicode, config=True,
588 588 help="""extra paths to look for Javascript notebook extensions"""
589 589 )
590 590
591 591 @property
592 592 def nbextensions_path(self):
593 593 """The path to look for Javascript notebook extensions"""
594 594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
595 595
596 596 websocket_url = Unicode("", config=True,
597 597 help="""The base URL for websockets,
598 598 if it differs from the HTTP server (hint: it almost certainly doesn't).
599 599
600 600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
601 601 """
602 602 )
603 603 mathjax_url = Unicode("", config=True,
604 604 help="""The url for MathJax.js."""
605 605 )
606 606 def _mathjax_url_default(self):
607 607 if not self.enable_mathjax:
608 608 return u''
609 609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
610 610 url_path_join(self.base_url, "static")
611 611 )
612 612
613 613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
614 614 for (url_prefix, search_path) in [
615 615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
616 616 (static_url_prefix, self.static_file_path),
617 617 ]:
618 618 self.log.debug("searching for local mathjax in %s", search_path)
619 619 try:
620 620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
621 621 except IOError:
622 622 continue
623 623 else:
624 624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
625 625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
626 626 return url
627 627
628 628 # no local mathjax, serve from CDN
629 629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
630 630 self.log.info("Using MathJax from CDN: %s", url)
631 631 return url
632 632
633 633 def _mathjax_url_changed(self, name, old, new):
634 634 if new and not self.enable_mathjax:
635 635 # enable_mathjax=False overrides mathjax_url
636 636 self.mathjax_url = u''
637 637 else:
638 638 self.log.info("Using MathJax: %s", new)
639 639
640 640 contents_manager_class = Type(
641 641 default_value=FileContentsManager,
642 642 klass=ContentsManager,
643 643 config=True,
644 644 help='The notebook manager class to use.'
645 645 )
646 646 kernel_manager_class = Type(
647 647 default_value=MappingKernelManager,
648 648 config=True,
649 649 help='The kernel manager class to use.'
650 650 )
651 651 session_manager_class = Type(
652 652 default_value=SessionManager,
653 653 config=True,
654 654 help='The session manager class to use.'
655 655 )
656 656 cluster_manager_class = Type(
657 657 default_value=ClusterManager,
658 658 config=True,
659 659 help='The cluster manager class to use.'
660 660 )
661 661
662 662 config_manager_class = Type(
663 663 default_value=ConfigManager,
664 664 config = True,
665 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 670 kernel_spec_manager_class = Type(
671 671 default_value=KernelSpecManager,
672 672 config=True,
673 673 help="""
674 674 The kernel spec manager class to use. Should be a subclass
675 675 of `IPython.kernel.kernelspec.KernelSpecManager`.
676 676
677 677 The Api of KernelSpecManager is provisional and might change
678 678 without warning between this version of IPython and the next stable one.
679 679 """
680 680 )
681 681
682 682 login_handler_class = Type(
683 683 default_value=LoginHandler,
684 684 klass=web.RequestHandler,
685 685 config=True,
686 686 help='The login handler class to use.',
687 687 )
688 688
689 689 logout_handler_class = Type(
690 690 default_value=LogoutHandler,
691 691 klass=web.RequestHandler,
692 692 config=True,
693 693 help='The logout handler class to use.',
694 694 )
695 695
696 696 trust_xheaders = Bool(False, config=True,
697 697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
698 698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
699 699 )
700 700
701 701 info_file = Unicode()
702 702
703 703 def _info_file_default(self):
704 704 info_file = "nbserver-%s.json"%os.getpid()
705 705 return os.path.join(self.profile_dir.security_dir, info_file)
706 706
707 707 pylab = Unicode('disabled', config=True,
708 708 help="""
709 709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
710 710 """
711 711 )
712 712 def _pylab_changed(self, name, old, new):
713 713 """when --pylab is specified, display a warning and exit"""
714 714 if new != 'warn':
715 715 backend = ' %s' % new
716 716 else:
717 717 backend = ''
718 718 self.log.error("Support for specifying --pylab on the command line has been removed.")
719 719 self.log.error(
720 720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
721 721 )
722 722 self.exit(1)
723 723
724 724 notebook_dir = Unicode(config=True,
725 725 help="The directory to use for notebooks and kernels."
726 726 )
727 727
728 728 def _notebook_dir_default(self):
729 729 if self.file_to_run:
730 730 return os.path.dirname(os.path.abspath(self.file_to_run))
731 731 else:
732 732 return py3compat.getcwd()
733 733
734 734 def _notebook_dir_changed(self, name, old, new):
735 735 """Do a bit of validation of the notebook dir."""
736 736 if not os.path.isabs(new):
737 737 # If we receive a non-absolute path, make it absolute.
738 738 self.notebook_dir = os.path.abspath(new)
739 739 return
740 740 if not os.path.isdir(new):
741 741 raise TraitError("No such notebook dir: %r" % new)
742 742
743 743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
744 744 self.config.FileContentsManager.root_dir = new
745 745 self.config.MappingKernelManager.root_dir = new
746 746
747 747 server_extensions = List(Unicode(), config=True,
748 748 help=("Python modules to load as notebook server extensions. "
749 749 "This is an experimental API, and may change in future releases.")
750 750 )
751 751
752 752 reraise_server_extension_failures = Bool(
753 753 False,
754 754 config=True,
755 755 help="Reraise exceptions encountered loading server extensions?",
756 756 )
757 757
758 758 def parse_command_line(self, argv=None):
759 759 super(NotebookApp, self).parse_command_line(argv)
760 760
761 761 if self.extra_args:
762 762 arg0 = self.extra_args[0]
763 763 f = os.path.abspath(arg0)
764 764 self.argv.remove(arg0)
765 765 if not os.path.exists(f):
766 766 self.log.critical("No such file or directory: %s", f)
767 767 self.exit(1)
768 768
769 769 # Use config here, to ensure that it takes higher priority than
770 770 # anything that comes from the profile.
771 771 c = Config()
772 772 if os.path.isdir(f):
773 773 c.NotebookApp.notebook_dir = f
774 774 elif os.path.isfile(f):
775 775 c.NotebookApp.file_to_run = f
776 776 self.update_config(c)
777 777
778 778 def init_configurables(self):
779 779 self.kernel_spec_manager = self.kernel_spec_manager_class(
780 780 parent=self,
781 781 ipython_dir=self.ipython_dir,
782 782 )
783 783 self.kernel_manager = self.kernel_manager_class(
784 784 parent=self,
785 785 log=self.log,
786 786 connection_dir=self.profile_dir.security_dir,
787 787 )
788 788 self.contents_manager = self.contents_manager_class(
789 789 parent=self,
790 790 log=self.log,
791 791 )
792 792 self.session_manager = self.session_manager_class(
793 793 parent=self,
794 794 log=self.log,
795 795 kernel_manager=self.kernel_manager,
796 796 contents_manager=self.contents_manager,
797 797 )
798 798 self.cluster_manager = self.cluster_manager_class(
799 799 parent=self,
800 800 log=self.log,
801 801 )
802 802
803 803 self.config_manager = self.config_manager_class(
804 804 parent=self,
805 805 log=self.log,
806 806 profile_dir=self.profile_dir.location,
807 807 )
808 808
809 809 def init_logging(self):
810 810 # This prevents double log messages because tornado use a root logger that
811 811 # self.log is a child of. The logging module dipatches log messages to a log
812 812 # and all of its ancenstors until propagate is set to False.
813 813 self.log.propagate = False
814 814
815 815 for log in app_log, access_log, gen_log:
816 816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
817 817 log.name = self.log.name
818 818 # hook up tornado 3's loggers to our app handlers
819 819 logger = logging.getLogger('tornado')
820 820 logger.propagate = True
821 821 logger.parent = self.log
822 822 logger.setLevel(self.log.level)
823 823
824 824 def init_webapp(self):
825 825 """initialize tornado webapp and httpserver"""
826 826 self.tornado_settings['allow_origin'] = self.allow_origin
827 827 if self.allow_origin_pat:
828 828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
829 829 self.tornado_settings['allow_credentials'] = self.allow_credentials
830 830 # ensure default_url starts with base_url
831 831 if not self.default_url.startswith(self.base_url):
832 832 self.default_url = url_path_join(self.base_url, self.default_url)
833 833
834 834 self.web_app = NotebookWebApplication(
835 835 self, self.kernel_manager, self.contents_manager,
836 836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
837 837 self.config_manager,
838 838 self.log, self.base_url, self.default_url, self.tornado_settings,
839 839 self.jinja_environment_options
840 840 )
841 841 ssl_options = self.ssl_options
842 842 if self.certfile:
843 843 ssl_options['certfile'] = self.certfile
844 844 if self.keyfile:
845 845 ssl_options['keyfile'] = self.keyfile
846 846 if not ssl_options:
847 847 # None indicates no SSL config
848 848 ssl_options = None
849 849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
850 850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
851 851 xheaders=self.trust_xheaders)
852 852
853 853 success = None
854 854 for port in random_ports(self.port, self.port_retries+1):
855 855 try:
856 856 self.http_server.listen(port, self.ip)
857 857 except socket.error as e:
858 858 if e.errno == errno.EADDRINUSE:
859 859 self.log.info('The port %i is already in use, trying another random port.' % port)
860 860 continue
861 861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
862 862 self.log.warn("Permission to listen on port %i denied" % port)
863 863 continue
864 864 else:
865 865 raise
866 866 else:
867 867 self.port = port
868 868 success = True
869 869 break
870 870 if not success:
871 871 self.log.critical('ERROR: the notebook server could not be started because '
872 872 'no available port could be found.')
873 873 self.exit(1)
874 874
875 875 @property
876 876 def display_url(self):
877 877 ip = self.ip if self.ip else '[all ip addresses on your system]'
878 878 return self._url(ip)
879 879
880 880 @property
881 881 def connection_url(self):
882 882 ip = self.ip if self.ip else 'localhost'
883 883 return self._url(ip)
884 884
885 885 def _url(self, ip):
886 886 proto = 'https' if self.certfile else 'http'
887 887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
888 888
889 889 def init_terminals(self):
890 890 try:
891 891 from .terminal import initialize
892 892 initialize(self.web_app)
893 893 self.web_app.settings['terminals_available'] = True
894 894 except ImportError as e:
895 895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
896 896 log("Terminals not available (error was %s)", e)
897 897
898 898 def init_signal(self):
899 899 if not sys.platform.startswith('win'):
900 900 signal.signal(signal.SIGINT, self._handle_sigint)
901 901 signal.signal(signal.SIGTERM, self._signal_stop)
902 902 if hasattr(signal, 'SIGUSR1'):
903 903 # Windows doesn't support SIGUSR1
904 904 signal.signal(signal.SIGUSR1, self._signal_info)
905 905 if hasattr(signal, 'SIGINFO'):
906 906 # only on BSD-based systems
907 907 signal.signal(signal.SIGINFO, self._signal_info)
908 908
909 909 def _handle_sigint(self, sig, frame):
910 910 """SIGINT handler spawns confirmation dialog"""
911 911 # register more forceful signal handler for ^C^C case
912 912 signal.signal(signal.SIGINT, self._signal_stop)
913 913 # request confirmation dialog in bg thread, to avoid
914 914 # blocking the App
915 915 thread = threading.Thread(target=self._confirm_exit)
916 916 thread.daemon = True
917 917 thread.start()
918 918
919 919 def _restore_sigint_handler(self):
920 920 """callback for restoring original SIGINT handler"""
921 921 signal.signal(signal.SIGINT, self._handle_sigint)
922 922
923 923 def _confirm_exit(self):
924 924 """confirm shutdown on ^C
925 925
926 926 A second ^C, or answering 'y' within 5s will cause shutdown,
927 927 otherwise original SIGINT handler will be restored.
928 928
929 929 This doesn't work on Windows.
930 930 """
931 931 info = self.log.info
932 932 info('interrupted')
933 933 print(self.notebook_info())
934 934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
935 935 sys.stdout.flush()
936 936 r,w,x = select.select([sys.stdin], [], [], 5)
937 937 if r:
938 938 line = sys.stdin.readline()
939 939 if line.lower().startswith('y') and 'n' not in line.lower():
940 940 self.log.critical("Shutdown confirmed")
941 941 ioloop.IOLoop.current().stop()
942 942 return
943 943 else:
944 944 print("No answer for 5s:", end=' ')
945 945 print("resuming operation...")
946 946 # no answer, or answer is no:
947 947 # set it back to original SIGINT handler
948 948 # use IOLoop.add_callback because signal.signal must be called
949 949 # from main thread
950 950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
951 951
952 952 def _signal_stop(self, sig, frame):
953 953 self.log.critical("received signal %s, stopping", sig)
954 954 ioloop.IOLoop.current().stop()
955 955
956 956 def _signal_info(self, sig, frame):
957 957 print(self.notebook_info())
958 958
959 959 def init_components(self):
960 960 """Check the components submodule, and warn if it's unclean"""
961 961 status = submodule.check_submodule_status()
962 962 if status == 'missing':
963 963 self.log.warn("components submodule missing, running `git submodule update`")
964 964 submodule.update_submodules(submodule.ipython_parent())
965 965 elif status == 'unclean':
966 966 self.log.warn("components submodule unclean, you may see 404s on static/components")
967 967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
968 968
969 969 def init_server_extensions(self):
970 970 """Load any extensions specified by config.
971 971
972 972 Import the module, then call the load_jupyter_server_extension function,
973 973 if one exists.
974 974
975 975 The extension API is experimental, and may change in future releases.
976 976 """
977 977 for modulename in self.server_extensions:
978 978 try:
979 979 mod = importlib.import_module(modulename)
980 980 func = getattr(mod, 'load_jupyter_server_extension', None)
981 981 if func is not None:
982 982 func(self)
983 983 except Exception:
984 984 if self.reraise_server_extension_failures:
985 985 raise
986 986 self.log.warn("Error loading server extension %s", modulename,
987 987 exc_info=True)
988 988
989 989 @catch_config_error
990 990 def initialize(self, argv=None):
991 991 super(NotebookApp, self).initialize(argv)
992 992 self.init_logging()
993 993 self.init_configurables()
994 994 self.init_components()
995 995 self.init_webapp()
996 996 self.init_terminals()
997 997 self.init_signal()
998 998 self.init_server_extensions()
999 999
1000 1000 def cleanup_kernels(self):
1001 1001 """Shutdown all kernels.
1002 1002
1003 1003 The kernels will shutdown themselves when this process no longer exists,
1004 1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1005 1005 """
1006 1006 self.log.info('Shutting down kernels')
1007 1007 self.kernel_manager.shutdown_all()
1008 1008
1009 1009 def notebook_info(self):
1010 1010 "Return the current working directory and the server url information"
1011 1011 info = self.contents_manager.info_string() + "\n"
1012 1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1013 1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1014 1014
1015 1015 def server_info(self):
1016 1016 """Return a JSONable dict of information about this server."""
1017 1017 return {'url': self.connection_url,
1018 1018 'hostname': self.ip if self.ip else 'localhost',
1019 1019 'port': self.port,
1020 1020 'secure': bool(self.certfile),
1021 1021 'base_url': self.base_url,
1022 1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1023 1023 'pid': os.getpid()
1024 1024 }
1025 1025
1026 1026 def write_server_info_file(self):
1027 1027 """Write the result of server_info() to the JSON file info_file."""
1028 1028 with open(self.info_file, 'w') as f:
1029 1029 json.dump(self.server_info(), f, indent=2)
1030 1030
1031 1031 def remove_server_info_file(self):
1032 1032 """Remove the nbserver-<pid>.json file created for this server.
1033 1033
1034 1034 Ignores the error raised when the file has already been removed.
1035 1035 """
1036 1036 try:
1037 1037 os.unlink(self.info_file)
1038 1038 except OSError as e:
1039 1039 if e.errno != errno.ENOENT:
1040 1040 raise
1041 1041
1042 1042 def start(self):
1043 1043 """ Start the IPython Notebook server app, after initialization
1044 1044
1045 1045 This method takes no arguments so all configuration and initialization
1046 1046 must be done prior to calling this method."""
1047 1047 if self.subapp is not None:
1048 1048 return self.subapp.start()
1049 1049
1050 1050 info = self.log.info
1051 1051 for line in self.notebook_info().split("\n"):
1052 1052 info(line)
1053 1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1054 1054
1055 1055 self.write_server_info_file()
1056 1056
1057 1057 if self.open_browser or self.file_to_run:
1058 1058 try:
1059 1059 browser = webbrowser.get(self.browser or None)
1060 1060 except webbrowser.Error as e:
1061 1061 self.log.warn('No web browser found: %s.' % e)
1062 1062 browser = None
1063 1063
1064 1064 if self.file_to_run:
1065 1065 if not os.path.exists(self.file_to_run):
1066 1066 self.log.critical("%s does not exist" % self.file_to_run)
1067 1067 self.exit(1)
1068 1068
1069 1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1070 1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1071 1071 else:
1072 1072 uri = 'tree'
1073 1073 if browser:
1074 1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1075 1075 new=2)
1076 1076 threading.Thread(target=b).start()
1077 1077
1078 1078 self.io_loop = ioloop.IOLoop.current()
1079 1079 if sys.platform.startswith('win'):
1080 1080 # add no-op to wake every 5s
1081 1081 # to handle signals that may be ignored by the inner loop
1082 1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1083 1083 pc.start()
1084 1084 try:
1085 1085 self.io_loop.start()
1086 1086 except KeyboardInterrupt:
1087 1087 info("Interrupted...")
1088 1088 finally:
1089 1089 self.cleanup_kernels()
1090 1090 self.remove_server_info_file()
1091 1091
1092 1092 def stop(self):
1093 1093 def _stop():
1094 1094 self.http_server.stop()
1095 1095 self.io_loop.stop()
1096 1096 self.io_loop.add_callback(_stop)
1097 1097
1098 1098
1099 1099 def list_running_servers(profile='default'):
1100 1100 """Iterate over the server info files of running notebook servers.
1101 1101
1102 1102 Given a profile name, find nbserver-* files in the security directory of
1103 1103 that profile, and yield dicts of their information, each one pertaining to
1104 1104 a currently running notebook server instance.
1105 1105 """
1106 1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1107 1107 for file in os.listdir(pd.security_dir):
1108 1108 if file.startswith('nbserver-'):
1109 1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1110 1110 info = json.load(f)
1111 1111
1112 1112 # Simple check whether that process is really still running
1113 1113 # Also remove leftover files from IPython 2.x without a pid field
1114 1114 if ('pid' in info) and check_pid(info['pid']):
1115 1115 yield info
1116 1116 else:
1117 1117 # If the process has died, try to delete its info file
1118 1118 try:
1119 1119 os.unlink(file)
1120 1120 except OSError:
1121 1121 pass # TODO: This should warn or log or something
1122 1122 #-----------------------------------------------------------------------------
1123 1123 # Main entry point
1124 1124 #-----------------------------------------------------------------------------
1125 1125
1126 1126 launch_new_instance = NotebookApp.launch_instance
1127 1127
@@ -1,162 +1,162 b''
1 1 """Manage IPython.parallel clusters in the notebook."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from tornado import web
7 7
8 8 from IPython.config.configurable import LoggingConfigurable
9 9 from IPython.utils.traitlets import Dict, Instance, Float
10 10 from IPython.core.profileapp import list_profiles_in
11 11 from IPython.core.profiledir import ProfileDir
12 12 from IPython.utils import py3compat
13 13 from IPython.utils.path import get_ipython_dir
14 14
15 15
16 16 class ClusterManager(LoggingConfigurable):
17 17
18 18 profiles = Dict()
19 19
20 20 delay = Float(1., config=True,
21 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 24 def _loop_default(self):
25 25 from zmq.eventloop.ioloop import IOLoop
26 26 return IOLoop.instance()
27 27
28 28 def build_launchers(self, profile_dir):
29 29 from IPython.parallel.apps.ipclusterapp import IPClusterStart
30 30
31 31 class DummyIPClusterStart(IPClusterStart):
32 32 """Dummy subclass to skip init steps that conflict with global app.
33 33
34 34 Instantiating and initializing this class should result in fully configured
35 35 launchers, but no other side effects or state.
36 36 """
37 37
38 38 def init_signal(self):
39 39 pass
40 40 def reinit_logging(self):
41 41 pass
42 42
43 43 starter = DummyIPClusterStart(log=self.log)
44 44 starter.initialize(['--profile-dir', profile_dir])
45 45 cl = starter.controller_launcher
46 46 esl = starter.engine_launcher
47 47 n = starter.n
48 48 return cl, esl, n
49 49
50 50 def get_profile_dir(self, name, path):
51 51 p = ProfileDir.find_profile_dir_by_name(path,name=name)
52 52 return p.location
53 53
54 54 def update_profiles(self):
55 55 """List all profiles in the ipython_dir and cwd.
56 56 """
57 57
58 58 stale = set(self.profiles)
59 59 for path in [get_ipython_dir(), py3compat.getcwd()]:
60 60 for profile in list_profiles_in(path):
61 61 if profile in stale:
62 62 stale.remove(profile)
63 63 pd = self.get_profile_dir(profile, path)
64 64 if profile not in self.profiles:
65 65 self.log.debug("Adding cluster profile '%s'", profile)
66 66 self.profiles[profile] = {
67 67 'profile': profile,
68 68 'profile_dir': pd,
69 69 'status': 'stopped'
70 70 }
71 71 for profile in stale:
72 72 # remove profiles that no longer exist
73 73 self.log.debug("Profile '%s' no longer exists", profile)
74 74 self.profiles.pop(stale)
75 75
76 76 def list_profiles(self):
77 77 self.update_profiles()
78 78 # sorted list, but ensure that 'default' always comes first
79 79 default_first = lambda name: name if name != 'default' else ''
80 80 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
81 81 return result
82 82
83 83 def check_profile(self, profile):
84 84 if profile not in self.profiles:
85 85 raise web.HTTPError(404, u'profile not found')
86 86
87 87 def profile_info(self, profile):
88 88 self.check_profile(profile)
89 89 result = {}
90 90 data = self.profiles.get(profile)
91 91 result['profile'] = profile
92 92 result['profile_dir'] = data['profile_dir']
93 93 result['status'] = data['status']
94 94 if 'n' in data:
95 95 result['n'] = data['n']
96 96 return result
97 97
98 98 def start_cluster(self, profile, n=None):
99 99 """Start a cluster for a given profile."""
100 100 self.check_profile(profile)
101 101 data = self.profiles[profile]
102 102 if data['status'] == 'running':
103 103 raise web.HTTPError(409, u'cluster already running')
104 104 cl, esl, default_n = self.build_launchers(data['profile_dir'])
105 105 n = n if n is not None else default_n
106 106 def clean_data():
107 107 data.pop('controller_launcher',None)
108 108 data.pop('engine_set_launcher',None)
109 109 data.pop('n',None)
110 110 data['status'] = 'stopped'
111 111 def engines_stopped(r):
112 112 self.log.debug('Engines stopped')
113 113 if cl.running:
114 114 cl.stop()
115 115 clean_data()
116 116 esl.on_stop(engines_stopped)
117 117 def controller_stopped(r):
118 118 self.log.debug('Controller stopped')
119 119 if esl.running:
120 120 esl.stop()
121 121 clean_data()
122 122 cl.on_stop(controller_stopped)
123 123 loop = self.loop
124 124
125 125 def start():
126 126 """start the controller, then the engines after a delay"""
127 127 cl.start()
128 128 loop.add_timeout(self.loop.time() + self.delay, lambda : esl.start(n))
129 129 self.loop.add_callback(start)
130 130
131 131 self.log.debug('Cluster started')
132 132 data['controller_launcher'] = cl
133 133 data['engine_set_launcher'] = esl
134 134 data['n'] = n
135 135 data['status'] = 'running'
136 136 return self.profile_info(profile)
137 137
138 138 def stop_cluster(self, profile):
139 139 """Stop a cluster for a given profile."""
140 140 self.check_profile(profile)
141 141 data = self.profiles[profile]
142 142 if data['status'] == 'stopped':
143 143 raise web.HTTPError(409, u'cluster not running')
144 144 data = self.profiles[profile]
145 145 cl = data['controller_launcher']
146 146 esl = data['engine_set_launcher']
147 147 if cl.running:
148 148 cl.stop()
149 149 if esl.running:
150 150 esl.stop()
151 151 # Return a temp info dict, the real one is updated in the on_stop
152 152 # logic above.
153 153 result = {
154 154 'profile': data['profile'],
155 155 'profile_dir': data['profile_dir'],
156 156 'status': 'stopped'
157 157 }
158 158 return result
159 159
160 160 def stop_all_clusters(self):
161 161 for p in self.profiles.keys():
162 162 self.stop_cluster(p)
@@ -1,497 +1,497 b''
1 1 """Base Widget class. Allows user to create widgets in the back-end that render
2 2 in the IPython notebook front-end.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the Modified BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from contextlib import contextmanager
16 16 import collections
17 17
18 18 from IPython.core.getipython import get_ipython
19 19 from IPython.kernel.comm import Comm
20 20 from IPython.config import LoggingConfigurable
21 21 from IPython.utils.importstring import import_item
22 22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 24 from IPython.utils.py3compat import string_types
25 25 from .trait_types import Color
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Classes
29 29 #-----------------------------------------------------------------------------
30 30 class CallbackDispatcher(LoggingConfigurable):
31 31 """A structure for registering and running callbacks"""
32 32 callbacks = List()
33 33
34 34 def __call__(self, *args, **kwargs):
35 35 """Call all of the registered callbacks."""
36 36 value = None
37 37 for callback in self.callbacks:
38 38 try:
39 39 local_value = callback(*args, **kwargs)
40 40 except Exception as e:
41 41 ip = get_ipython()
42 42 if ip is None:
43 43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
44 44 else:
45 45 ip.showtraceback()
46 46 else:
47 47 value = local_value if local_value is not None else value
48 48 return value
49 49
50 50 def register_callback(self, callback, remove=False):
51 51 """(Un)Register a callback
52 52
53 53 Parameters
54 54 ----------
55 55 callback: method handle
56 56 Method to be registered or unregistered.
57 57 remove=False: bool
58 58 Whether to unregister the callback."""
59 59
60 60 # (Un)Register the callback.
61 61 if remove and callback in self.callbacks:
62 62 self.callbacks.remove(callback)
63 63 elif not remove and callback not in self.callbacks:
64 64 self.callbacks.append(callback)
65 65
66 66 def _show_traceback(method):
67 67 """decorator for showing tracebacks in IPython"""
68 68 def m(self, *args, **kwargs):
69 69 try:
70 70 return(method(self, *args, **kwargs))
71 71 except Exception as e:
72 72 ip = get_ipython()
73 73 if ip is None:
74 74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
75 75 else:
76 76 ip.showtraceback()
77 77 return m
78 78
79 79
80 80 def register(key=None):
81 81 """Returns a decorator registering a widget class in the widget registry.
82 82 If no key is provided, the class name is used as a key. A key is
83 83 provided for each core IPython widget so that the frontend can use
84 84 this key regardless of the language of the kernel"""
85 85 def wrap(widget):
86 86 l = key if key is not None else widget.__module__ + widget.__name__
87 87 Widget.widget_types[l] = widget
88 88 return widget
89 89 return wrap
90 90
91 91
92 92 class Widget(LoggingConfigurable):
93 93 #-------------------------------------------------------------------------
94 94 # Class attributes
95 95 #-------------------------------------------------------------------------
96 96 _widget_construction_callback = None
97 97 widgets = {}
98 98 widget_types = {}
99 99
100 100 @staticmethod
101 101 def on_widget_constructed(callback):
102 102 """Registers a callback to be called when a widget is constructed.
103 103
104 104 The callback must have the following signature:
105 105 callback(widget)"""
106 106 Widget._widget_construction_callback = callback
107 107
108 108 @staticmethod
109 109 def _call_widget_constructed(widget):
110 110 """Static method, called when a widget is constructed."""
111 111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
112 112 Widget._widget_construction_callback(widget)
113 113
114 114 @staticmethod
115 115 def handle_comm_opened(comm, msg):
116 116 """Static method, called when a widget is constructed."""
117 117 widget_class = import_item(msg['content']['data']['widget_class'])
118 118 widget = widget_class(comm=comm)
119 119
120 120
121 121 #-------------------------------------------------------------------------
122 122 # Traits
123 123 #-------------------------------------------------------------------------
124 124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
125 125 in which to find _model_name. If empty, look in the global registry.""")
126 126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
127 127 registered in the front-end to create and sync this widget with.""")
128 128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
129 129 If empty, look in the global registry.""", sync=True)
130 130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
131 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 134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
135 135 front-end can send before receiving an idle msg from the back-end.""")
136 136
137 137 version = Int(0, sync=True, help="""Widget's version""")
138 138 keys = List()
139 139 def _keys_default(self):
140 140 return [name for name in self.traits(sync=True)]
141 141
142 142 _property_lock = Tuple((None, None))
143 143 _send_state_lock = Int(0)
144 144 _states_to_send = Set()
145 145 _display_callbacks = Instance(CallbackDispatcher, ())
146 146 _msg_callbacks = Instance(CallbackDispatcher, ())
147 147
148 148 #-------------------------------------------------------------------------
149 149 # (Con/de)structor
150 150 #-------------------------------------------------------------------------
151 151 def __init__(self, **kwargs):
152 152 """Public constructor"""
153 153 self._model_id = kwargs.pop('model_id', None)
154 154 super(Widget, self).__init__(**kwargs)
155 155
156 156 Widget._call_widget_constructed(self)
157 157 self.open()
158 158
159 159 def __del__(self):
160 160 """Object disposal"""
161 161 self.close()
162 162
163 163 #-------------------------------------------------------------------------
164 164 # Properties
165 165 #-------------------------------------------------------------------------
166 166
167 167 def open(self):
168 168 """Open a comm to the frontend if one isn't already open."""
169 169 if self.comm is None:
170 170 args = dict(target_name='ipython.widget',
171 171 data={'model_name': self._model_name,
172 172 'model_module': self._model_module})
173 173 if self._model_id is not None:
174 174 args['comm_id'] = self._model_id
175 175 self.comm = Comm(**args)
176 176
177 177 def _comm_changed(self, name, new):
178 178 """Called when the comm is changed."""
179 179 if new is None:
180 180 return
181 181 self._model_id = self.model_id
182 182
183 183 self.comm.on_msg(self._handle_msg)
184 184 Widget.widgets[self.model_id] = self
185 185
186 186 # first update
187 187 self.send_state()
188 188
189 189 @property
190 190 def model_id(self):
191 191 """Gets the model id of this widget.
192 192
193 193 If a Comm doesn't exist yet, a Comm will be created automagically."""
194 194 return self.comm.comm_id
195 195
196 196 #-------------------------------------------------------------------------
197 197 # Methods
198 198 #-------------------------------------------------------------------------
199 199
200 200 def close(self):
201 201 """Close method.
202 202
203 203 Closes the underlying comm.
204 204 When the comm is closed, all of the widget views are automatically
205 205 removed from the front-end."""
206 206 if self.comm is not None:
207 207 Widget.widgets.pop(self.model_id, None)
208 208 self.comm.close()
209 209 self.comm = None
210 210
211 211 def send_state(self, key=None):
212 212 """Sends the widget state, or a piece of it, to the front-end.
213 213
214 214 Parameters
215 215 ----------
216 216 key : unicode, or iterable (optional)
217 217 A single property's name or iterable of property names to sync with the front-end.
218 218 """
219 219 self._send({
220 220 "method" : "update",
221 221 "state" : self.get_state(key=key)
222 222 })
223 223
224 224 def get_state(self, key=None):
225 225 """Gets the widget state, or a piece of it.
226 226
227 227 Parameters
228 228 ----------
229 229 key : unicode or iterable (optional)
230 230 A single property's name or iterable of property names to get.
231 231 """
232 232 if key is None:
233 233 keys = self.keys
234 234 elif isinstance(key, string_types):
235 235 keys = [key]
236 236 elif isinstance(key, collections.Iterable):
237 237 keys = key
238 238 else:
239 239 raise ValueError("key must be a string, an iterable of keys, or None")
240 240 state = {}
241 241 for k in keys:
242 242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
243 243 value = getattr(self, k)
244 244 state[k] = f(value)
245 245 return state
246 246
247 247 def set_state(self, sync_data):
248 248 """Called when a state is received from the front-end."""
249 249 for name in self.keys:
250 250 if name in sync_data:
251 251 json_value = sync_data[name]
252 252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
253 253 with self._lock_property(name, json_value):
254 254 setattr(self, name, from_json(json_value))
255 255
256 256 def send(self, content):
257 257 """Sends a custom msg to the widget model in the front-end.
258 258
259 259 Parameters
260 260 ----------
261 261 content : dict
262 262 Content of the message to send.
263 263 """
264 264 self._send({"method": "custom", "content": content})
265 265
266 266 def on_msg(self, callback, remove=False):
267 267 """(Un)Register a custom msg receive callback.
268 268
269 269 Parameters
270 270 ----------
271 271 callback: callable
272 272 callback will be passed two arguments when a message arrives::
273 273
274 274 callback(widget, content)
275 275
276 276 remove: bool
277 277 True if the callback should be unregistered."""
278 278 self._msg_callbacks.register_callback(callback, remove=remove)
279 279
280 280 def on_displayed(self, callback, remove=False):
281 281 """(Un)Register a widget displayed callback.
282 282
283 283 Parameters
284 284 ----------
285 285 callback: method handler
286 286 Must have a signature of::
287 287
288 288 callback(widget, **kwargs)
289 289
290 290 kwargs from display are passed through without modification.
291 291 remove: bool
292 292 True if the callback should be unregistered."""
293 293 self._display_callbacks.register_callback(callback, remove=remove)
294 294
295 295 def add_trait(self, traitname, trait):
296 296 """Dynamically add a trait attribute to the Widget."""
297 297 super(Widget, self).add_trait(traitname, trait)
298 298 if trait.get_metadata('sync'):
299 299 self.keys.append(traitname)
300 300 self.send_state(traitname)
301 301
302 302 #-------------------------------------------------------------------------
303 303 # Support methods
304 304 #-------------------------------------------------------------------------
305 305 @contextmanager
306 306 def _lock_property(self, key, value):
307 307 """Lock a property-value pair.
308 308
309 309 The value should be the JSON state of the property.
310 310
311 311 NOTE: This, in addition to the single lock for all state changes, is
312 312 flawed. In the future we may want to look into buffering state changes
313 313 back to the front-end."""
314 314 self._property_lock = (key, value)
315 315 try:
316 316 yield
317 317 finally:
318 318 self._property_lock = (None, None)
319 319
320 320 @contextmanager
321 321 def hold_sync(self):
322 322 """Hold syncing any state until the context manager is released"""
323 323 # We increment a value so that this can be nested. Syncing will happen when
324 324 # all levels have been released.
325 325 self._send_state_lock += 1
326 326 try:
327 327 yield
328 328 finally:
329 329 self._send_state_lock -=1
330 330 if self._send_state_lock == 0:
331 331 self.send_state(self._states_to_send)
332 332 self._states_to_send.clear()
333 333
334 334 def _should_send_property(self, key, value):
335 335 """Check the property lock (property_lock)"""
336 336 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
337 337 if (key == self._property_lock[0]
338 338 and to_json(value) == self._property_lock[1]):
339 339 return False
340 340 elif self._send_state_lock > 0:
341 341 self._states_to_send.add(key)
342 342 return False
343 343 else:
344 344 return True
345 345
346 346 # Event handlers
347 347 @_show_traceback
348 348 def _handle_msg(self, msg):
349 349 """Called when a msg is received from the front-end"""
350 350 data = msg['content']['data']
351 351 method = data['method']
352 352
353 353 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
354 354 if method == 'backbone':
355 355 if 'sync_data' in data:
356 356 sync_data = data['sync_data']
357 357 self.set_state(sync_data) # handles all methods
358 358
359 359 # Handle a state request.
360 360 elif method == 'request_state':
361 361 self.send_state()
362 362
363 363 # Handle a custom msg from the front-end.
364 364 elif method == 'custom':
365 365 if 'content' in data:
366 366 self._handle_custom_msg(data['content'])
367 367
368 368 # Catch remainder.
369 369 else:
370 370 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
371 371
372 372 def _handle_custom_msg(self, content):
373 373 """Called when a custom msg is received."""
374 374 self._msg_callbacks(self, content)
375 375
376 376 def _notify_trait(self, name, old_value, new_value):
377 377 """Called when a property has been changed."""
378 378 # Trigger default traitlet callback machinery. This allows any user
379 379 # registered validation to be processed prior to allowing the widget
380 380 # machinery to handle the state.
381 381 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
382 382
383 383 # Send the state after the user registered callbacks for trait changes
384 384 # have all fired (allows for user to validate values).
385 385 if self.comm is not None and name in self.keys:
386 386 # Make sure this isn't information that the front-end just sent us.
387 387 if self._should_send_property(name, new_value):
388 388 # Send new state to front-end
389 389 self.send_state(key=name)
390 390
391 391 def _handle_displayed(self, **kwargs):
392 392 """Called when a view has been displayed for this widget instance"""
393 393 self._display_callbacks(self, **kwargs)
394 394
395 395 def _trait_to_json(self, x):
396 396 """Convert a trait value to json
397 397
398 398 Traverse lists/tuples and dicts and serialize their values as well.
399 399 Replace any widgets with their model_id
400 400 """
401 401 if isinstance(x, dict):
402 402 return {k: self._trait_to_json(v) for k, v in x.items()}
403 403 elif isinstance(x, (list, tuple)):
404 404 return [self._trait_to_json(v) for v in x]
405 405 elif isinstance(x, Widget):
406 406 return "IPY_MODEL_" + x.model_id
407 407 else:
408 408 return x # Value must be JSON-able
409 409
410 410 def _trait_from_json(self, x):
411 411 """Convert json values to objects
412 412
413 413 Replace any strings representing valid model id values to Widget references.
414 414 """
415 415 if isinstance(x, dict):
416 416 return {k: self._trait_from_json(v) for k, v in x.items()}
417 417 elif isinstance(x, (list, tuple)):
418 418 return [self._trait_from_json(v) for v in x]
419 419 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
420 420 # we want to support having child widgets at any level in a hierarchy
421 421 # trusting that a widget UUID will not appear out in the wild
422 422 return Widget.widgets[x[10:]]
423 423 else:
424 424 return x
425 425
426 426 def _ipython_display_(self, **kwargs):
427 427 """Called when `IPython.display.display` is called on the widget."""
428 428 # Show view.
429 429 if self._view_name is not None:
430 430 self._send({"method": "display"})
431 431 self._handle_displayed(**kwargs)
432 432
433 433 def _send(self, msg):
434 434 """Sends a message to the model in the front-end."""
435 435 self.comm.send(msg)
436 436
437 437
438 438 class DOMWidget(Widget):
439 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 440 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
441 441 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
442 442
443 443 width = CUnicode(sync=True)
444 444 height = CUnicode(sync=True)
445 445 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
446 446 padding = CUnicode(sync=True)
447 447 margin = CUnicode(sync=True)
448 448
449 449 color = Color(None, allow_none=True, sync=True)
450 450 background_color = Color(None, allow_none=True, sync=True)
451 451 border_color = Color(None, allow_none=True, sync=True)
452 452
453 453 border_width = CUnicode(sync=True)
454 454 border_radius = CUnicode(sync=True)
455 455 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
456 456 'none',
457 457 'hidden',
458 458 'dotted',
459 459 'dashed',
460 460 'solid',
461 461 'double',
462 462 'groove',
463 463 'ridge',
464 464 'inset',
465 465 'outset',
466 466 'initial',
467 467 'inherit', ''],
468 468 default_value='', sync=True)
469 469
470 470 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
471 471 'normal',
472 472 'italic',
473 473 'oblique',
474 474 'initial',
475 475 'inherit', ''],
476 476 default_value='', sync=True)
477 477 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
478 478 'normal',
479 479 'bold',
480 480 'bolder',
481 481 'lighter',
482 482 'initial',
483 483 'inherit', ''] + list(map(str, range(100,1000,100))),
484 484 default_value='', sync=True)
485 485 font_size = CUnicode(sync=True)
486 486 font_family = Unicode(sync=True)
487 487
488 488 def __init__(self, *pargs, **kwargs):
489 489 super(DOMWidget, self).__init__(*pargs, **kwargs)
490 490
491 491 def _validate_border(name, old, new):
492 492 if new is not None and new != '':
493 493 if name != 'border_width' and not self.border_width:
494 494 self.border_width = 1
495 495 if name != 'border_style' and self.border_style == '':
496 496 self.border_style = 'solid'
497 497 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,156 +1,157 b''
1 1 """A client for in-process kernels."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2012 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # IPython imports
15 15 from IPython.kernel.inprocess.socket import DummySocket
16 16 from IPython.utils.traitlets import Type, Instance
17 17 from IPython.kernel.clientabc import KernelClientABC
18 18 from IPython.kernel.client import KernelClient
19 19
20 20 # Local imports
21 21 from .channels import (
22 22 InProcessChannel,
23 23 InProcessHBChannel,
24 24
25 25 )
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Main kernel Client class
29 29 #-----------------------------------------------------------------------------
30 30
31 31 class InProcessKernelClient(KernelClient):
32 32 """A client for an in-process kernel.
33 33
34 34 This class implements the interface of
35 35 `IPython.kernel.clientabc.KernelClientABC` and allows
36 36 (asynchronous) frontends to be used seamlessly with an in-process kernel.
37 37
38 38 See `IPython.kernel.client.KernelClient` for docstrings.
39 39 """
40 40
41 41 # The classes to use for the various channels.
42 42 shell_channel_class = Type(InProcessChannel)
43 43 iopub_channel_class = Type(InProcessChannel)
44 44 stdin_channel_class = Type(InProcessChannel)
45 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 51 # Channel management methods
51 52 #--------------------------------------------------------------------------
52 53
53 54 def start_channels(self, *args, **kwargs):
54 55 super(InProcessKernelClient, self).start_channels(self)
55 56 self.kernel.frontends.append(self)
56 57
57 58 @property
58 59 def shell_channel(self):
59 60 if self._shell_channel is None:
60 61 self._shell_channel = self.shell_channel_class(self)
61 62 return self._shell_channel
62 63
63 64 @property
64 65 def iopub_channel(self):
65 66 if self._iopub_channel is None:
66 67 self._iopub_channel = self.iopub_channel_class(self)
67 68 return self._iopub_channel
68 69
69 70 @property
70 71 def stdin_channel(self):
71 72 if self._stdin_channel is None:
72 73 self._stdin_channel = self.stdin_channel_class(self)
73 74 return self._stdin_channel
74 75
75 76 @property
76 77 def hb_channel(self):
77 78 if self._hb_channel is None:
78 79 self._hb_channel = self.hb_channel_class(self)
79 80 return self._hb_channel
80 81
81 82 # Methods for sending specific messages
82 83 # -------------------------------------
83 84
84 85 def execute(self, code, silent=False, store_history=True,
85 86 user_expressions={}, allow_stdin=None):
86 87 if allow_stdin is None:
87 88 allow_stdin = self.allow_stdin
88 89 content = dict(code=code, silent=silent, store_history=store_history,
89 90 user_expressions=user_expressions,
90 91 allow_stdin=allow_stdin)
91 92 msg = self.session.msg('execute_request', content)
92 93 self._dispatch_to_kernel(msg)
93 94 return msg['header']['msg_id']
94 95
95 96 def complete(self, code, cursor_pos=None):
96 97 if cursor_pos is None:
97 98 cursor_pos = len(code)
98 99 content = dict(code=code, cursor_pos=cursor_pos)
99 100 msg = self.session.msg('complete_request', content)
100 101 self._dispatch_to_kernel(msg)
101 102 return msg['header']['msg_id']
102 103
103 104 def inspect(self, code, cursor_pos=None, detail_level=0):
104 105 if cursor_pos is None:
105 106 cursor_pos = len(code)
106 107 content = dict(code=code, cursor_pos=cursor_pos,
107 108 detail_level=detail_level,
108 109 )
109 110 msg = self.session.msg('inspect_request', content)
110 111 self._dispatch_to_kernel(msg)
111 112 return msg['header']['msg_id']
112 113
113 114 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 115 content = dict(raw=raw, output=output,
115 116 hist_access_type=hist_access_type, **kwds)
116 117 msg = self.session.msg('history_request', content)
117 118 self._dispatch_to_kernel(msg)
118 119 return msg['header']['msg_id']
119 120
120 121 def shutdown(self, restart=False):
121 122 # FIXME: What to do here?
122 123 raise NotImplementedError('Cannot shutdown in-process kernel')
123 124
124 125 def kernel_info(self):
125 126 """Request kernel info."""
126 127 msg = self.session.msg('kernel_info_request')
127 128 self._dispatch_to_kernel(msg)
128 129 return msg['header']['msg_id']
129 130
130 131 def input(self, string):
131 132 if self.kernel is None:
132 133 raise RuntimeError('Cannot send input reply. No kernel exists.')
133 134 self.kernel.raw_input_str = string
134 135
135 136
136 137 def _dispatch_to_kernel(self, msg):
137 138 """ Send a message to the kernel and handle a reply.
138 139 """
139 140 kernel = self.kernel
140 141 if kernel is None:
141 142 raise RuntimeError('Cannot send request. No kernel exists.')
142 143
143 144 stream = DummySocket()
144 145 self.session.send(stream, msg)
145 146 msg_parts = stream.recv_multipart()
146 147 kernel.dispatch_shell(stream, msg_parts)
147 148
148 149 idents, reply_msg = self.session.recv(stream, copy=False)
149 150 self.shell_channel.call_handlers_later(reply_msg)
150 151
151 152
152 153 #-----------------------------------------------------------------------------
153 154 # ABC Registration
154 155 #-----------------------------------------------------------------------------
155 156
156 157 KernelClientABC.register(InProcessKernelClient)
@@ -1,169 +1,171 b''
1 1 """An in-process kernel"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from contextlib import contextmanager
7 7 import logging
8 8 import sys
9 9
10 10 from IPython.core.interactiveshell import InteractiveShellABC
11 11 from IPython.utils.jsonutil import json_clean
12 12 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
13 13 from IPython.kernel.zmq.ipkernel import IPythonKernel
14 14 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
15 15
16 16 from .socket import DummySocket
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Main kernel class
20 20 #-----------------------------------------------------------------------------
21 21
22 22 class InProcessKernel(IPythonKernel):
23 23
24 24 #-------------------------------------------------------------------------
25 25 # InProcessKernel interface
26 26 #-------------------------------------------------------------------------
27 27
28 28 # The frontends connected to this kernel.
29 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 34 # The GUI environment that the kernel is running under. This need not be
34 35 # specified for the normal operation for the kernel, but is required for
35 36 # IPython's GUI support (including pylab). The default is 'inline' because
36 37 # it is safe under all GUI toolkits.
37 38 gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'),
38 39 default_value='inline')
39 40
40 41 raw_input_str = Any()
41 42 stdout = Any()
42 43 stderr = Any()
43 44
44 45 #-------------------------------------------------------------------------
45 46 # Kernel interface
46 47 #-------------------------------------------------------------------------
47 48
48 49 shell_class = Type()
49 50 shell_streams = List()
50 51 control_stream = Any()
51 52 iopub_socket = Instance(DummySocket, ())
52 53 stdin_socket = Instance(DummySocket, ())
53 54
54 55 def __init__(self, **traits):
55 56 super(InProcessKernel, self).__init__(**traits)
56 57
57 58 self.iopub_socket.on_trait_change(self._io_dispatch, 'message_sent')
58 59 self.shell.kernel = self
59 60
60 61 def execute_request(self, stream, ident, parent):
61 62 """ Override for temporary IO redirection. """
62 63 with self._redirected_io():
63 64 super(InProcessKernel, self).execute_request(stream, ident, parent)
64 65
65 66 def start(self):
66 67 """ Override registration of dispatchers for streams. """
67 68 self.shell.exit_now = False
68 69
69 70 def _abort_queue(self, stream):
70 71 """ The in-process kernel doesn't abort requests. """
71 72 pass
72 73
73 74 def _input_request(self, prompt, ident, parent, password=False):
74 75 # Flush output before making the request.
75 76 self.raw_input_str = None
76 77 sys.stderr.flush()
77 78 sys.stdout.flush()
78 79
79 80 # Send the input request.
80 81 content = json_clean(dict(prompt=prompt, password=password))
81 82 msg = self.session.msg(u'input_request', content, parent)
82 83 for frontend in self.frontends:
83 84 if frontend.session.session == parent['header']['session']:
84 85 frontend.stdin_channel.call_handlers(msg)
85 86 break
86 87 else:
87 88 logging.error('No frontend found for raw_input request')
88 89 return str()
89 90
90 91 # Await a response.
91 92 while self.raw_input_str is None:
92 93 frontend.stdin_channel.process_events()
93 94 return self.raw_input_str
94 95
95 96 #-------------------------------------------------------------------------
96 97 # Protected interface
97 98 #-------------------------------------------------------------------------
98 99
99 100 @contextmanager
100 101 def _redirected_io(self):
101 102 """ Temporarily redirect IO to the kernel.
102 103 """
103 104 sys_stdout, sys_stderr = sys.stdout, sys.stderr
104 105 sys.stdout, sys.stderr = self.stdout, self.stderr
105 106 yield
106 107 sys.stdout, sys.stderr = sys_stdout, sys_stderr
107 108
108 109 #------ Trait change handlers --------------------------------------------
109 110
110 111 def _io_dispatch(self):
111 112 """ Called when a message is sent to the IO socket.
112 113 """
113 114 ident, msg = self.session.recv(self.iopub_socket, copy=False)
114 115 for frontend in self.frontends:
115 116 frontend.iopub_channel.call_handlers(msg)
116 117
117 118 #------ Trait initializers -----------------------------------------------
118 119
119 120 def _log_default(self):
120 121 return logging.getLogger(__name__)
121 122
122 123 def _session_default(self):
123 124 from IPython.kernel.zmq.session import Session
124 125 return Session(parent=self, key=b'')
125 126
126 127 def _shell_class_default(self):
127 128 return InProcessInteractiveShell
128 129
129 130 def _stdout_default(self):
130 131 from IPython.kernel.zmq.iostream import OutStream
131 132 return OutStream(self.session, self.iopub_socket, u'stdout', pipe=False)
132 133
133 134 def _stderr_default(self):
134 135 from IPython.kernel.zmq.iostream import OutStream
135 136 return OutStream(self.session, self.iopub_socket, u'stderr', pipe=False)
136 137
137 138 #-----------------------------------------------------------------------------
138 139 # Interactive shell subclass
139 140 #-----------------------------------------------------------------------------
140 141
141 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 148 # InteractiveShell interface
147 149 #-------------------------------------------------------------------------
148 150
149 151 def enable_gui(self, gui=None):
150 152 """Enable GUI integration for the kernel."""
151 153 from IPython.kernel.zmq.eventloops import enable_gui
152 154 if not gui:
153 155 gui = self.kernel.gui
154 156 return enable_gui(gui, kernel=self.kernel)
155 157
156 158 def enable_matplotlib(self, gui=None):
157 159 """Enable matplotlib integration for the kernel."""
158 160 if not gui:
159 161 gui = self.kernel.gui
160 162 return super(InProcessInteractiveShell, self).enable_matplotlib(gui)
161 163
162 164 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
163 165 """Activate pylab support at runtime."""
164 166 if not gui:
165 167 gui = self.kernel.gui
166 168 return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all,
167 169 welcome_message)
168 170
169 171 InteractiveShellABC.register(InProcessInteractiveShell)
@@ -1,71 +1,72 b''
1 1 """A kernel manager for in-process kernels."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from IPython.utils.traitlets import Instance, DottedObjectName
7 7 from IPython.kernel.managerabc import KernelManagerABC
8 8 from IPython.kernel.manager import KernelManager
9 9 from IPython.kernel.zmq.session import Session
10 10
11 11
12 12 class InProcessKernelManager(KernelManager):
13 13 """A manager for an in-process kernel.
14 14
15 15 This class implements the interface of
16 16 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
17 17 (asynchronous) frontends to be used seamlessly with an in-process kernel.
18 18
19 19 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
20 20 """
21 21
22 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 25 # the client class for KM.client() shortcut
25 26 client_class = DottedObjectName('IPython.kernel.inprocess.BlockingInProcessKernelClient')
26 27
27 28 def _session_default(self):
28 29 # don't sign in-process messages
29 30 return Session(key=b'', parent=self)
30 31
31 32 #--------------------------------------------------------------------------
32 33 # Kernel management methods
33 34 #--------------------------------------------------------------------------
34 35
35 36 def start_kernel(self, **kwds):
36 37 from IPython.kernel.inprocess.ipkernel import InProcessKernel
37 38 self.kernel = InProcessKernel(parent=self, session=self.session)
38 39
39 40 def shutdown_kernel(self):
40 41 self._kill_kernel()
41 42
42 43 def restart_kernel(self, now=False, **kwds):
43 44 self.shutdown_kernel()
44 45 self.start_kernel(**kwds)
45 46
46 47 @property
47 48 def has_kernel(self):
48 49 return self.kernel is not None
49 50
50 51 def _kill_kernel(self):
51 52 self.kernel = None
52 53
53 54 def interrupt_kernel(self):
54 55 raise NotImplementedError("Cannot interrupt in-process kernel.")
55 56
56 57 def signal_kernel(self, signum):
57 58 raise NotImplementedError("Cannot signal in-process kernel.")
58 59
59 60 def is_alive(self):
60 61 return self.kernel is not None
61 62
62 63 def client(self, **kwargs):
63 64 kwargs['kernel'] = self.kernel
64 65 return super(InProcessKernelManager, self).client(**kwargs)
65 66
66 67
67 68 #-----------------------------------------------------------------------------
68 69 # ABC Registration
69 70 #-----------------------------------------------------------------------------
70 71
71 72 KernelManagerABC.register(InProcessKernelManager)
@@ -1,62 +1,62 b''
1 1 """A kernel manager with a tornado IOLoop"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 from __future__ import absolute_import
15 15
16 16 from zmq.eventloop import ioloop
17 17 from zmq.eventloop.zmqstream import ZMQStream
18 18
19 19 from IPython.utils.traitlets import (
20 20 Instance
21 21 )
22 22
23 23 from IPython.kernel.manager import KernelManager
24 24 from .restarter import IOLoopKernelRestarter
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Code
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 def as_zmqstream(f):
32 32 def wrapped(self, *args, **kwargs):
33 33 socket = f(self, *args, **kwargs)
34 34 return ZMQStream(socket, self.loop)
35 35 return wrapped
36 36
37 37 class IOLoopKernelManager(KernelManager):
38 38
39 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
39 loop = Instance('zmq.eventloop.ioloop.IOLoop')
40 40 def _loop_default(self):
41 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 45 def start_restarter(self):
46 46 if self.autorestart and self.has_kernel:
47 47 if self._restarter is None:
48 48 self._restarter = IOLoopKernelRestarter(
49 49 kernel_manager=self, loop=self.loop,
50 50 parent=self, log=self.log
51 51 )
52 52 self._restarter.start()
53 53
54 54 def stop_restarter(self):
55 55 if self.autorestart:
56 56 if self._restarter is not None:
57 57 self._restarter.stop()
58 58
59 59 connect_shell = as_zmqstream(KernelManager.connect_shell)
60 60 connect_iopub = as_zmqstream(KernelManager.connect_iopub)
61 61 connect_stdin = as_zmqstream(KernelManager.connect_stdin)
62 62 connect_hb = as_zmqstream(KernelManager.connect_hb)
@@ -1,54 +1,54 b''
1 1 """A basic in process kernel monitor with autorestarting.
2 2
3 3 This watches a kernel's state using KernelManager.is_alive and auto
4 4 restarts the kernel if it dies.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2013 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import absolute_import
19 19
20 20 from zmq.eventloop import ioloop
21 21
22 22
23 23 from IPython.kernel.restarter import KernelRestarter
24 24 from IPython.utils.traitlets import (
25 25 Instance,
26 26 )
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Code
30 30 #-----------------------------------------------------------------------------
31 31
32 32 class IOLoopKernelRestarter(KernelRestarter):
33 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 36 def _loop_default(self):
37 37 return ioloop.IOLoop.instance()
38 38
39 39 _pcallback = None
40 40
41 41 def start(self):
42 42 """Start the polling of the kernel."""
43 43 if self._pcallback is None:
44 44 self._pcallback = ioloop.PeriodicCallback(
45 45 self.poll, 1000*self.time_to_dead, self.loop
46 46 )
47 47 self._pcallback.start()
48 48
49 49 def stop(self):
50 50 """Stop the kernel polling."""
51 51 if self._pcallback is not None:
52 52 self._pcallback.stop()
53 53 self._pcallback = None
54 54
@@ -1,70 +1,70 b''
1 1 """Publishing native (typically pickled) objects.
2 2 """
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2012 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 from IPython.config import Configurable
16 16 from IPython.kernel.inprocess.socket import SocketABC
17 17 from IPython.utils.jsonutil import json_clean
18 18 from IPython.utils.traitlets import Instance, Dict, CBytes
19 19 from IPython.kernel.zmq.serialize import serialize_object
20 20 from IPython.kernel.zmq.session import Session, extract_header
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Code
24 24 #-----------------------------------------------------------------------------
25 25
26 26
27 27 class ZMQDataPublisher(Configurable):
28 28
29 29 topic = topic = CBytes(b'datapub')
30 session = Instance(Session)
31 pub_socket = Instance(SocketABC)
30 session = Instance(Session, allow_none=True)
31 pub_socket = Instance(SocketABC, allow_none=True)
32 32 parent_header = Dict({})
33 33
34 34 def set_parent(self, parent):
35 35 """Set the parent for outbound messages."""
36 36 self.parent_header = extract_header(parent)
37 37
38 38 def publish_data(self, data):
39 39 """publish a data_message on the IOPub channel
40 40
41 41 Parameters
42 42 ----------
43 43
44 44 data : dict
45 45 The data to be published. Think of it as a namespace.
46 46 """
47 47 session = self.session
48 48 buffers = serialize_object(data,
49 49 buffer_threshold=session.buffer_threshold,
50 50 item_threshold=session.item_threshold,
51 51 )
52 52 content = json_clean(dict(keys=data.keys()))
53 53 session.send(self.pub_socket, 'data_message', content=content,
54 54 parent=self.parent_header,
55 55 buffers=buffers,
56 56 ident=self.topic,
57 57 )
58 58
59 59
60 60 def publish_data(data):
61 61 """publish a data_message on the IOPub channel
62 62
63 63 Parameters
64 64 ----------
65 65
66 66 data : dict
67 67 The data to be published. Think of it as a namespace.
68 68 """
69 69 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
70 70 ZMQInteractiveShell.instance().data_pub.publish_data(data)
@@ -1,74 +1,74 b''
1 1 """Replacements for sys.displayhook that publish over ZMQ."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import sys
7 7
8 8 from IPython.core.displayhook import DisplayHook
9 9 from IPython.kernel.inprocess.socket import SocketABC
10 10 from IPython.utils.jsonutil import encode_images
11 11 from IPython.utils.py3compat import builtin_mod
12 12 from IPython.utils.traitlets import Instance, Dict
13 13 from .session import extract_header, Session
14 14
15 15 class ZMQDisplayHook(object):
16 16 """A simple displayhook that publishes the object's repr over a ZeroMQ
17 17 socket."""
18 18 topic=b'execute_result'
19 19
20 20 def __init__(self, session, pub_socket):
21 21 self.session = session
22 22 self.pub_socket = pub_socket
23 23 self.parent_header = {}
24 24
25 25 def __call__(self, obj):
26 26 if obj is None:
27 27 return
28 28
29 29 builtin_mod._ = obj
30 30 sys.stdout.flush()
31 31 sys.stderr.flush()
32 32 msg = self.session.send(self.pub_socket, u'execute_result', {u'data':repr(obj)},
33 33 parent=self.parent_header, ident=self.topic)
34 34
35 35 def set_parent(self, parent):
36 36 self.parent_header = extract_header(parent)
37 37
38 38
39 39 class ZMQShellDisplayHook(DisplayHook):
40 40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
41 41 to work with an InteractiveShell instance. It sends a dict of different
42 42 representations of the object."""
43 43 topic=None
44 44
45 session = Instance(Session)
46 pub_socket = Instance(SocketABC)
45 session = Instance(Session, allow_none=True)
46 pub_socket = Instance(SocketABC, allow_none=True)
47 47 parent_header = Dict({})
48 48
49 49 def set_parent(self, parent):
50 50 """Set the parent for outbound messages."""
51 51 self.parent_header = extract_header(parent)
52 52
53 53 def start_displayhook(self):
54 54 self.msg = self.session.msg(u'execute_result', {
55 55 'data': {},
56 56 'metadata': {},
57 57 }, parent=self.parent_header)
58 58
59 59 def write_output_prompt(self):
60 60 """Write the output prompt."""
61 61 self.msg['content']['execution_count'] = self.prompt_count
62 62
63 63 def write_format_data(self, format_dict, md_dict=None):
64 64 self.msg['content']['data'] = encode_images(format_dict)
65 65 self.msg['content']['metadata'] = md_dict
66 66
67 67 def finish_displayhook(self):
68 68 """Finish up all displayhook activities."""
69 69 sys.stdout.flush()
70 70 sys.stderr.flush()
71 71 if self.msg['content']['data']:
72 72 self.session.send(self.pub_socket, self.msg, ident=self.topic)
73 73 self.msg = None
74 74
@@ -1,367 +1,368 b''
1 1 """The IPython kernel implementation"""
2 2
3 3 import getpass
4 4 import sys
5 5 import traceback
6 6
7 7 from IPython.core import release
8 8 from IPython.utils.py3compat import builtin_mod, PY3
9 9 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
10 10 from IPython.utils.traitlets import Instance, Type, Any, List
11 11 from IPython.utils.decorators import undoc
12 12
13 13 from ..comm import CommManager
14 14 from .kernelbase import Kernel as KernelBase
15 15 from .serialize import serialize_object, unpack_apply_message
16 16 from .zmqshell import ZMQInteractiveShell
17 17
18 18
19 19 def lazy_import_handle_comm_opened(*args, **kwargs):
20 20 from IPython.html.widgets import Widget
21 21 Widget.handle_comm_opened(*args, **kwargs)
22 22
23 23
24 24 class IPythonKernel(KernelBase):
25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
26 allow_none=True)
26 27 shell_class = Type(ZMQInteractiveShell)
27 28
28 29 user_module = Any()
29 30 def _user_module_changed(self, name, old, new):
30 31 if self.shell is not None:
31 32 self.shell.user_module = new
32 33
33 34 user_ns = Instance(dict, args=None, allow_none=True)
34 35 def _user_ns_changed(self, name, old, new):
35 36 if self.shell is not None:
36 37 self.shell.user_ns = new
37 38 self.shell.init_user_ns()
38 39
39 40 # A reference to the Python builtin 'raw_input' function.
40 41 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
41 42 _sys_raw_input = Any()
42 43 _sys_eval_input = Any()
43 44
44 45 def __init__(self, **kwargs):
45 46 super(IPythonKernel, self).__init__(**kwargs)
46 47
47 48 # Initialize the InteractiveShell subclass
48 49 self.shell = self.shell_class.instance(parent=self,
49 50 profile_dir = self.profile_dir,
50 51 user_module = self.user_module,
51 52 user_ns = self.user_ns,
52 53 kernel = self,
53 54 )
54 55 self.shell.displayhook.session = self.session
55 56 self.shell.displayhook.pub_socket = self.iopub_socket
56 57 self.shell.displayhook.topic = self._topic('execute_result')
57 58 self.shell.display_pub.session = self.session
58 59 self.shell.display_pub.pub_socket = self.iopub_socket
59 60 self.shell.data_pub.session = self.session
60 61 self.shell.data_pub.pub_socket = self.iopub_socket
61 62
62 63 # TMP - hack while developing
63 64 self.shell._reply_content = None
64 65
65 66 self.comm_manager = CommManager(shell=self.shell, parent=self,
66 67 kernel=self)
67 68 self.comm_manager.register_target('ipython.widget', lazy_import_handle_comm_opened)
68 69
69 70 self.shell.configurables.append(self.comm_manager)
70 71 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
71 72 for msg_type in comm_msg_types:
72 73 self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type)
73 74
74 75 help_links = List([
75 76 {
76 77 'text': "Python",
77 78 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2],
78 79 },
79 80 {
80 81 'text': "IPython",
81 82 'url': "http://ipython.org/documentation.html",
82 83 },
83 84 {
84 85 'text': "NumPy",
85 86 'url': "http://docs.scipy.org/doc/numpy/reference/",
86 87 },
87 88 {
88 89 'text': "SciPy",
89 90 'url': "http://docs.scipy.org/doc/scipy/reference/",
90 91 },
91 92 {
92 93 'text': "Matplotlib",
93 94 'url': "http://matplotlib.org/contents.html",
94 95 },
95 96 {
96 97 'text': "SymPy",
97 98 'url': "http://docs.sympy.org/latest/index.html",
98 99 },
99 100 {
100 101 'text': "pandas",
101 102 'url': "http://pandas.pydata.org/pandas-docs/stable/",
102 103 },
103 104 ])
104 105
105 106 # Kernel info fields
106 107 implementation = 'ipython'
107 108 implementation_version = release.version
108 109 language_info = {
109 110 'name': 'python',
110 111 'version': sys.version.split()[0],
111 112 'mimetype': 'text/x-python',
112 113 'codemirror_mode': {'name': 'ipython',
113 114 'version': sys.version_info[0]},
114 115 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2),
115 116 'nbconvert_exporter': 'python',
116 117 'file_extension': '.py'
117 118 }
118 119 @property
119 120 def banner(self):
120 121 return self.shell.banner
121 122
122 123 def start(self):
123 124 self.shell.exit_now = False
124 125 super(IPythonKernel, self).start()
125 126
126 127 def set_parent(self, ident, parent):
127 128 """Overridden from parent to tell the display hook and output streams
128 129 about the parent message.
129 130 """
130 131 super(IPythonKernel, self).set_parent(ident, parent)
131 132 self.shell.set_parent(parent)
132 133
133 134 def _forward_input(self, allow_stdin=False):
134 135 """Forward raw_input and getpass to the current frontend.
135 136
136 137 via input_request
137 138 """
138 139 self._allow_stdin = allow_stdin
139 140
140 141 if PY3:
141 142 self._sys_raw_input = builtin_mod.input
142 143 builtin_mod.input = self.raw_input
143 144 else:
144 145 self._sys_raw_input = builtin_mod.raw_input
145 146 self._sys_eval_input = builtin_mod.input
146 147 builtin_mod.raw_input = self.raw_input
147 148 builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt))
148 149 self._save_getpass = getpass.getpass
149 150 getpass.getpass = self.getpass
150 151
151 152 def _restore_input(self):
152 153 """Restore raw_input, getpass"""
153 154 if PY3:
154 155 builtin_mod.input = self._sys_raw_input
155 156 else:
156 157 builtin_mod.raw_input = self._sys_raw_input
157 158 builtin_mod.input = self._sys_eval_input
158 159
159 160 getpass.getpass = self._save_getpass
160 161
161 162 @property
162 163 def execution_count(self):
163 164 return self.shell.execution_count
164 165
165 166 @execution_count.setter
166 167 def execution_count(self, value):
167 168 # Ignore the incrememnting done by KernelBase, in favour of our shell's
168 169 # execution counter.
169 170 pass
170 171
171 172 def do_execute(self, code, silent, store_history=True,
172 173 user_expressions=None, allow_stdin=False):
173 174 shell = self.shell # we'll need this a lot here
174 175
175 176 self._forward_input(allow_stdin)
176 177
177 178 reply_content = {}
178 179 # FIXME: the shell calls the exception handler itself.
179 180 shell._reply_content = None
180 181 try:
181 182 shell.run_cell(code, store_history=store_history, silent=silent)
182 183 except:
183 184 status = u'error'
184 185 # FIXME: this code right now isn't being used yet by default,
185 186 # because the run_cell() call above directly fires off exception
186 187 # reporting. This code, therefore, is only active in the scenario
187 188 # where runlines itself has an unhandled exception. We need to
188 189 # uniformize this, for all exception construction to come from a
189 190 # single location in the codbase.
190 191 etype, evalue, tb = sys.exc_info()
191 192 tb_list = traceback.format_exception(etype, evalue, tb)
192 193 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
193 194 else:
194 195 status = u'ok'
195 196 finally:
196 197 self._restore_input()
197 198
198 199 reply_content[u'status'] = status
199 200
200 201 # Return the execution counter so clients can display prompts
201 202 reply_content['execution_count'] = shell.execution_count - 1
202 203
203 204 # FIXME - fish exception info out of shell, possibly left there by
204 205 # runlines. We'll need to clean up this logic later.
205 206 if shell._reply_content is not None:
206 207 reply_content.update(shell._reply_content)
207 208 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
208 209 reply_content['engine_info'] = e_info
209 210 # reset after use
210 211 shell._reply_content = None
211 212
212 213 if 'traceback' in reply_content:
213 214 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
214 215
215 216
216 217 # At this point, we can tell whether the main code execution succeeded
217 218 # or not. If it did, we proceed to evaluate user_expressions
218 219 if reply_content['status'] == 'ok':
219 220 reply_content[u'user_expressions'] = \
220 221 shell.user_expressions(user_expressions or {})
221 222 else:
222 223 # If there was an error, don't even try to compute expressions
223 224 reply_content[u'user_expressions'] = {}
224 225
225 226 # Payloads should be retrieved regardless of outcome, so we can both
226 227 # recover partial output (that could have been generated early in a
227 228 # block, before an error) and clear the payload system always.
228 229 reply_content[u'payload'] = shell.payload_manager.read_payload()
229 230 # Be agressive about clearing the payload because we don't want
230 231 # it to sit in memory until the next execute_request comes in.
231 232 shell.payload_manager.clear_payload()
232 233
233 234 return reply_content
234 235
235 236 def do_complete(self, code, cursor_pos):
236 237 # FIXME: IPython completers currently assume single line,
237 238 # but completion messages give multi-line context
238 239 # For now, extract line from cell, based on cursor_pos:
239 240 if cursor_pos is None:
240 241 cursor_pos = len(code)
241 242 line, offset = line_at_cursor(code, cursor_pos)
242 243 line_cursor = cursor_pos - offset
243 244
244 245 txt, matches = self.shell.complete('', line, line_cursor)
245 246 return {'matches' : matches,
246 247 'cursor_end' : cursor_pos,
247 248 'cursor_start' : cursor_pos - len(txt),
248 249 'metadata' : {},
249 250 'status' : 'ok'}
250 251
251 252 def do_inspect(self, code, cursor_pos, detail_level=0):
252 253 name = token_at_cursor(code, cursor_pos)
253 254 info = self.shell.object_inspect(name)
254 255
255 256 reply_content = {'status' : 'ok'}
256 257 reply_content['data'] = data = {}
257 258 reply_content['metadata'] = {}
258 259 reply_content['found'] = info['found']
259 260 if info['found']:
260 261 info_text = self.shell.object_inspect_text(
261 262 name,
262 263 detail_level=detail_level,
263 264 )
264 265 data['text/plain'] = info_text
265 266
266 267 return reply_content
267 268
268 269 def do_history(self, hist_access_type, output, raw, session=None, start=None,
269 270 stop=None, n=None, pattern=None, unique=False):
270 271 if hist_access_type == 'tail':
271 272 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
272 273 include_latest=True)
273 274
274 275 elif hist_access_type == 'range':
275 276 hist = self.shell.history_manager.get_range(session, start, stop,
276 277 raw=raw, output=output)
277 278
278 279 elif hist_access_type == 'search':
279 280 hist = self.shell.history_manager.search(
280 281 pattern, raw=raw, output=output, n=n, unique=unique)
281 282 else:
282 283 hist = []
283 284
284 285 return {'history' : list(hist)}
285 286
286 287 def do_shutdown(self, restart):
287 288 self.shell.exit_now = True
288 289 return dict(status='ok', restart=restart)
289 290
290 291 def do_is_complete(self, code):
291 292 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
292 293 r = {'status': status}
293 294 if status == 'incomplete':
294 295 r['indent'] = ' ' * indent_spaces
295 296 return r
296 297
297 298 def do_apply(self, content, bufs, msg_id, reply_metadata):
298 299 shell = self.shell
299 300 try:
300 301 working = shell.user_ns
301 302
302 303 prefix = "_"+str(msg_id).replace("-","")+"_"
303 304
304 305 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
305 306
306 307 fname = getattr(f, '__name__', 'f')
307 308
308 309 fname = prefix+"f"
309 310 argname = prefix+"args"
310 311 kwargname = prefix+"kwargs"
311 312 resultname = prefix+"result"
312 313
313 314 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
314 315 # print ns
315 316 working.update(ns)
316 317 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
317 318 try:
318 319 exec(code, shell.user_global_ns, shell.user_ns)
319 320 result = working.get(resultname)
320 321 finally:
321 322 for key in ns:
322 323 working.pop(key)
323 324
324 325 result_buf = serialize_object(result,
325 326 buffer_threshold=self.session.buffer_threshold,
326 327 item_threshold=self.session.item_threshold,
327 328 )
328 329
329 330 except:
330 331 # invoke IPython traceback formatting
331 332 shell.showtraceback()
332 333 # FIXME - fish exception info out of shell, possibly left there by
333 334 # run_code. We'll need to clean up this logic later.
334 335 reply_content = {}
335 336 if shell._reply_content is not None:
336 337 reply_content.update(shell._reply_content)
337 338 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
338 339 reply_content['engine_info'] = e_info
339 340 # reset after use
340 341 shell._reply_content = None
341 342
342 343 self.send_response(self.iopub_socket, u'error', reply_content,
343 344 ident=self._topic('error'))
344 345 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
345 346 result_buf = []
346 347
347 348 if reply_content['ename'] == 'UnmetDependency':
348 349 reply_metadata['dependencies_met'] = False
349 350 else:
350 351 reply_content = {'status' : 'ok'}
351 352
352 353 return reply_content, result_buf
353 354
354 355 def do_clear(self):
355 356 self.shell.reset(False)
356 357 return dict(status='ok')
357 358
358 359
359 360 # This exists only for backwards compatibility - use IPythonKernel instead
360 361
361 362 @undoc
362 363 class Kernel(IPythonKernel):
363 364 def __init__(self, *args, **kwargs):
364 365 import warnings
365 366 warnings.warn('Kernel is a deprecated alias of IPython.kernel.zmq.ipkernel.IPythonKernel',
366 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 1 """An Application for launching a kernel"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import print_function
7 7
8 8 import atexit
9 9 import os
10 10 import sys
11 11 import signal
12 12
13 13 import zmq
14 14 from zmq.eventloop import ioloop
15 15 from zmq.eventloop.zmqstream import ZMQStream
16 16
17 17 from IPython.core.ultratb import FormattedTB
18 18 from IPython.core.application import (
19 19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
20 20 )
21 21 from IPython.core.profiledir import ProfileDir
22 22 from IPython.core.shellapp import (
23 23 InteractiveShellApp, shell_flags, shell_aliases
24 24 )
25 25 from IPython.utils import io
26 26 from IPython.utils.path import filefind
27 27 from IPython.utils.traitlets import (
28 28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
29 29 )
30 30 from IPython.utils.importstring import import_item
31 31 from IPython.kernel import write_connection_file
32 32 from IPython.kernel.connect import ConnectionFileMixin
33 33
34 34 # local imports
35 35 from .heartbeat import Heartbeat
36 36 from .ipkernel import IPythonKernel
37 37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
38 38 from .session import (
39 39 Session, session_flags, session_aliases,
40 40 )
41 41 from .zmqshell import ZMQInteractiveShell
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Flags and Aliases
45 45 #-----------------------------------------------------------------------------
46 46
47 47 kernel_aliases = dict(base_aliases)
48 48 kernel_aliases.update({
49 49 'ip' : 'IPKernelApp.ip',
50 50 'hb' : 'IPKernelApp.hb_port',
51 51 'shell' : 'IPKernelApp.shell_port',
52 52 'iopub' : 'IPKernelApp.iopub_port',
53 53 'stdin' : 'IPKernelApp.stdin_port',
54 54 'control' : 'IPKernelApp.control_port',
55 55 'f' : 'IPKernelApp.connection_file',
56 56 'transport': 'IPKernelApp.transport',
57 57 })
58 58
59 59 kernel_flags = dict(base_flags)
60 60 kernel_flags.update({
61 61 'no-stdout' : (
62 62 {'IPKernelApp' : {'no_stdout' : True}},
63 63 "redirect stdout to the null device"),
64 64 'no-stderr' : (
65 65 {'IPKernelApp' : {'no_stderr' : True}},
66 66 "redirect stderr to the null device"),
67 67 'pylab' : (
68 68 {'IPKernelApp' : {'pylab' : 'auto'}},
69 69 """Pre-load matplotlib and numpy for interactive use with
70 70 the default matplotlib backend."""),
71 71 })
72 72
73 73 # inherit flags&aliases for any IPython shell apps
74 74 kernel_aliases.update(shell_aliases)
75 75 kernel_flags.update(shell_flags)
76 76
77 77 # inherit flags&aliases for Sessions
78 78 kernel_aliases.update(session_aliases)
79 79 kernel_flags.update(session_flags)
80 80
81 81 _ctrl_c_message = """\
82 82 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
83 83
84 84 To exit, you will have to explicitly quit this process, by either sending
85 85 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
86 86
87 87 To read more about this, see https://github.com/ipython/ipython/issues/2049
88 88
89 89 """
90 90
91 91 #-----------------------------------------------------------------------------
92 92 # Application class for starting an IPython Kernel
93 93 #-----------------------------------------------------------------------------
94 94
95 95 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
96 96 ConnectionFileMixin):
97 97 name='ipython-kernel'
98 98 aliases = Dict(kernel_aliases)
99 99 flags = Dict(kernel_flags)
100 100 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
101 101 # the kernel class, as an importstring
102 102 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
103 103 klass='IPython.kernel.zmq.kernelbase.Kernel',
104 104 help="""The Kernel subclass to be used.
105 105
106 106 This should allow easy re-use of the IPKernelApp entry point
107 107 to configure and launch kernels other than IPython's own.
108 108 """)
109 109 kernel = Any()
110 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 112 ports = Dict()
113 113
114 114 # connection info:
115 115
116 116 @property
117 117 def abs_connection_file(self):
118 118 if os.path.basename(self.connection_file) == self.connection_file:
119 119 return os.path.join(self.profile_dir.security_dir, self.connection_file)
120 120 else:
121 121 return self.connection_file
122 122
123 123
124 124 # streams, etc.
125 125 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
126 126 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
127 127 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
128 128 config=True, help="The importstring for the OutStream factory")
129 129 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
130 130 config=True, help="The importstring for the DisplayHook factory")
131 131
132 132 # polling
133 133 parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), config=True,
134 134 help="""kill this process if its parent dies. On Windows, the argument
135 135 specifies the HANDLE of the parent process, otherwise it is simply boolean.
136 136 """)
137 137 interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), config=True,
138 138 help="""ONLY USED ON WINDOWS
139 139 Interrupt this process when the parent is signaled.
140 140 """)
141 141
142 142 def init_crash_handler(self):
143 143 # Install minimal exception handling
144 144 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
145 145 ostream=sys.__stdout__)
146 146
147 147 def init_poller(self):
148 148 if sys.platform == 'win32':
149 149 if self.interrupt or self.parent_handle:
150 150 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
151 151 elif self.parent_handle:
152 152 self.poller = ParentPollerUnix()
153 153
154 154 def _bind_socket(self, s, port):
155 155 iface = '%s://%s' % (self.transport, self.ip)
156 156 if self.transport == 'tcp':
157 157 if port <= 0:
158 158 port = s.bind_to_random_port(iface)
159 159 else:
160 160 s.bind("tcp://%s:%i" % (self.ip, port))
161 161 elif self.transport == 'ipc':
162 162 if port <= 0:
163 163 port = 1
164 164 path = "%s-%i" % (self.ip, port)
165 165 while os.path.exists(path):
166 166 port = port + 1
167 167 path = "%s-%i" % (self.ip, port)
168 168 else:
169 169 path = "%s-%i" % (self.ip, port)
170 170 s.bind("ipc://%s" % path)
171 171 return port
172 172
173 173 def write_connection_file(self):
174 174 """write connection info to JSON file"""
175 175 cf = self.abs_connection_file
176 176 self.log.debug("Writing connection file: %s", cf)
177 177 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
178 178 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
179 179 iopub_port=self.iopub_port, control_port=self.control_port)
180 180
181 181 def cleanup_connection_file(self):
182 182 cf = self.abs_connection_file
183 183 self.log.debug("Cleaning up connection file: %s", cf)
184 184 try:
185 185 os.remove(cf)
186 186 except (IOError, OSError):
187 187 pass
188 188
189 189 self.cleanup_ipc_files()
190 190
191 191 def init_connection_file(self):
192 192 if not self.connection_file:
193 193 self.connection_file = "kernel-%s.json"%os.getpid()
194 194 try:
195 195 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
196 196 except IOError:
197 197 self.log.debug("Connection file not found: %s", self.connection_file)
198 198 # This means I own it, so I will clean it up:
199 199 atexit.register(self.cleanup_connection_file)
200 200 return
201 201 try:
202 202 self.load_connection_file()
203 203 except Exception:
204 204 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
205 205 self.exit(1)
206 206
207 207 def init_sockets(self):
208 208 # Create a context, a session, and the kernel sockets.
209 209 self.log.info("Starting the kernel at pid: %i", os.getpid())
210 210 context = zmq.Context.instance()
211 211 # Uncomment this to try closing the context.
212 212 # atexit.register(context.term)
213 213
214 214 self.shell_socket = context.socket(zmq.ROUTER)
215 215 self.shell_socket.linger = 1000
216 216 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
217 217 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
218 218
219 219 self.iopub_socket = context.socket(zmq.PUB)
220 220 self.iopub_socket.linger = 1000
221 221 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
222 222 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
223 223
224 224 self.stdin_socket = context.socket(zmq.ROUTER)
225 225 self.stdin_socket.linger = 1000
226 226 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
227 227 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
228 228
229 229 self.control_socket = context.socket(zmq.ROUTER)
230 230 self.control_socket.linger = 1000
231 231 self.control_port = self._bind_socket(self.control_socket, self.control_port)
232 232 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
233 233
234 234 def init_heartbeat(self):
235 235 """start the heart beating"""
236 236 # heartbeat doesn't share context, because it mustn't be blocked
237 237 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
238 238 hb_ctx = zmq.Context()
239 239 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
240 240 self.hb_port = self.heartbeat.port
241 241 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
242 242 self.heartbeat.start()
243 243
244 244 def log_connection_info(self):
245 245 """display connection info, and store ports"""
246 246 basename = os.path.basename(self.connection_file)
247 247 if basename == self.connection_file or \
248 248 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
249 249 # use shortname
250 250 tail = basename
251 251 if self.profile != 'default':
252 252 tail += " --profile %s" % self.profile
253 253 else:
254 254 tail = self.connection_file
255 255 lines = [
256 256 "To connect another client to this kernel, use:",
257 257 " --existing %s" % tail,
258 258 ]
259 259 # log connection info
260 260 # info-level, so often not shown.
261 261 # frontends should use the %connect_info magic
262 262 # to see the connection info
263 263 for line in lines:
264 264 self.log.info(line)
265 265 # also raw print to the terminal if no parent_handle (`ipython kernel`)
266 266 if not self.parent_handle:
267 267 io.rprint(_ctrl_c_message)
268 268 for line in lines:
269 269 io.rprint(line)
270 270
271 271 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
272 272 stdin=self.stdin_port, hb=self.hb_port,
273 273 control=self.control_port)
274 274
275 275 def init_blackhole(self):
276 276 """redirects stdout/stderr to devnull if necessary"""
277 277 if self.no_stdout or self.no_stderr:
278 278 blackhole = open(os.devnull, 'w')
279 279 if self.no_stdout:
280 280 sys.stdout = sys.__stdout__ = blackhole
281 281 if self.no_stderr:
282 282 sys.stderr = sys.__stderr__ = blackhole
283 283
284 284 def init_io(self):
285 285 """Redirect input streams and set a display hook."""
286 286 if self.outstream_class:
287 287 outstream_factory = import_item(str(self.outstream_class))
288 288 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
289 289 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
290 290 if self.displayhook_class:
291 291 displayhook_factory = import_item(str(self.displayhook_class))
292 292 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
293 293
294 294 def init_signal(self):
295 295 signal.signal(signal.SIGINT, signal.SIG_IGN)
296 296
297 297 def init_kernel(self):
298 298 """Create the Kernel object itself"""
299 299 shell_stream = ZMQStream(self.shell_socket)
300 300 control_stream = ZMQStream(self.control_socket)
301 301
302 302 kernel_factory = self.kernel_class.instance
303 303
304 304 kernel = kernel_factory(parent=self, session=self.session,
305 305 shell_streams=[shell_stream, control_stream],
306 306 iopub_socket=self.iopub_socket,
307 307 stdin_socket=self.stdin_socket,
308 308 log=self.log,
309 309 profile_dir=self.profile_dir,
310 310 user_ns=self.user_ns,
311 311 )
312 312 kernel.record_ports(self.ports)
313 313 self.kernel = kernel
314 314
315 315 def init_gui_pylab(self):
316 316 """Enable GUI event loop integration, taking pylab into account."""
317 317
318 318 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
319 319 # to ensure that any exception is printed straight to stderr.
320 320 # Normally _showtraceback associates the reply with an execution,
321 321 # which means frontends will never draw it, as this exception
322 322 # is not associated with any execute request.
323 323
324 324 shell = self.shell
325 325 _showtraceback = shell._showtraceback
326 326 try:
327 327 # replace error-sending traceback with stderr
328 328 def print_tb(etype, evalue, stb):
329 329 print ("GUI event loop or pylab initialization failed",
330 330 file=io.stderr)
331 331 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
332 332 shell._showtraceback = print_tb
333 333 InteractiveShellApp.init_gui_pylab(self)
334 334 finally:
335 335 shell._showtraceback = _showtraceback
336 336
337 337 def init_shell(self):
338 338 self.shell = getattr(self.kernel, 'shell', None)
339 339 if self.shell:
340 340 self.shell.configurables.append(self)
341 341
342 342 @catch_config_error
343 343 def initialize(self, argv=None):
344 344 super(IPKernelApp, self).initialize(argv)
345 345 self.init_blackhole()
346 346 self.init_connection_file()
347 347 self.init_poller()
348 348 self.init_sockets()
349 349 self.init_heartbeat()
350 350 # writing/displaying connection info must be *after* init_sockets/heartbeat
351 351 self.log_connection_info()
352 352 self.write_connection_file()
353 353 self.init_io()
354 354 self.init_signal()
355 355 self.init_kernel()
356 356 # shell init steps
357 357 self.init_path()
358 358 self.init_shell()
359 359 if self.shell:
360 360 self.init_gui_pylab()
361 361 self.init_extensions()
362 362 self.init_code()
363 363 # flush stdout/stderr, so that anything written to these streams during
364 364 # initialization do not get associated with the first execution request
365 365 sys.stdout.flush()
366 366 sys.stderr.flush()
367 367
368 368 def start(self):
369 369 if self.poller is not None:
370 370 self.poller.start()
371 371 self.kernel.start()
372 372 try:
373 373 ioloop.IOLoop.instance().start()
374 374 except KeyboardInterrupt:
375 375 pass
376 376
377 377 launch_new_instance = IPKernelApp.launch_instance
378 378
379 379 def main():
380 380 """Run an IPKernel as an application"""
381 381 app = IPKernelApp.instance()
382 382 app.initialize()
383 383 app.start()
384 384
385 385
386 386 if __name__ == '__main__':
387 387 main()
@@ -1,701 +1,701 b''
1 1 """Base class for a kernel that talks to frontends over 0MQ."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from __future__ import print_function
7 7
8 8 import sys
9 9 import time
10 10 import logging
11 11 import uuid
12 12
13 13 from datetime import datetime
14 14 from signal import (
15 15 signal, default_int_handler, SIGINT
16 16 )
17 17
18 18 import zmq
19 19 from zmq.eventloop import ioloop
20 20 from zmq.eventloop.zmqstream import ZMQStream
21 21
22 22 from IPython.config.configurable import SingletonConfigurable
23 23 from IPython.core.error import StdinNotImplementedError
24 24 from IPython.core import release
25 25 from IPython.utils import py3compat
26 26 from IPython.utils.py3compat import unicode_type, string_types
27 27 from IPython.utils.jsonutil import json_clean
28 28 from IPython.utils.traitlets import (
29 29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 30 )
31 31
32 32 from .session import Session
33 33
34 34
35 35 class Kernel(SingletonConfigurable):
36 36
37 37 #---------------------------------------------------------------------------
38 38 # Kernel interface
39 39 #---------------------------------------------------------------------------
40 40
41 41 # attribute to override with a GUI
42 42 eventloop = Any(None)
43 43 def _eventloop_changed(self, name, old, new):
44 44 """schedule call to eventloop from IOLoop"""
45 45 loop = ioloop.IOLoop.instance()
46 46 loop.add_callback(self.enter_eventloop)
47 47
48 session = Instance(Session)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
48 session = Instance(Session, allow_none=True)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True)
50 50 shell_streams = List()
51 control_stream = Instance(ZMQStream)
52 iopub_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
54 log = Instance(logging.Logger)
51 control_stream = Instance(ZMQStream, allow_none=True)
52 iopub_socket = Instance(zmq.Socket, allow_none=True)
53 stdin_socket = Instance(zmq.Socket, allow_none=True)
54 log = Instance(logging.Logger, allow_none=True)
55 55
56 56 # identities:
57 57 int_id = Integer(-1)
58 58 ident = Unicode()
59 59
60 60 def _ident_default(self):
61 61 return unicode_type(uuid.uuid4())
62 62
63 63 # This should be overridden by wrapper kernels that implement any real
64 64 # language.
65 65 language_info = {}
66 66
67 67 # any links that should go in the help menu
68 68 help_links = List()
69 69
70 70 # Private interface
71 71
72 72 _darwin_app_nap = Bool(True, config=True,
73 73 help="""Whether to use appnope for compatiblity with OS X App Nap.
74 74
75 75 Only affects OS X >= 10.9.
76 76 """
77 77 )
78 78
79 79 # track associations with current request
80 80 _allow_stdin = Bool(False)
81 81 _parent_header = Dict()
82 82 _parent_ident = Any(b'')
83 83 # Time to sleep after flushing the stdout/err buffers in each execute
84 84 # cycle. While this introduces a hard limit on the minimal latency of the
85 85 # execute cycle, it helps prevent output synchronization problems for
86 86 # clients.
87 87 # Units are in seconds. The minimum zmq latency on local host is probably
88 88 # ~150 microseconds, set this to 500us for now. We may need to increase it
89 89 # a little if it's not enough after more interactive testing.
90 90 _execute_sleep = Float(0.0005, config=True)
91 91
92 92 # Frequency of the kernel's event loop.
93 93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
94 94 # adapt to milliseconds.
95 95 _poll_interval = Float(0.05, config=True)
96 96
97 97 # If the shutdown was requested over the network, we leave here the
98 98 # necessary reply message so it can be sent by our registered atexit
99 99 # handler. This ensures that the reply is only sent to clients truly at
100 100 # the end of our shutdown process (which happens after the underlying
101 101 # IPython shell's own shutdown).
102 102 _shutdown_message = None
103 103
104 104 # This is a dict of port number that the kernel is listening on. It is set
105 105 # by record_ports and used by connect_request.
106 106 _recorded_ports = Dict()
107 107
108 108 # set of aborted msg_ids
109 109 aborted = Set()
110 110
111 111 # Track execution count here. For IPython, we override this to use the
112 112 # execution count we store in the shell.
113 113 execution_count = 0
114 114
115 115
116 116 def __init__(self, **kwargs):
117 117 super(Kernel, self).__init__(**kwargs)
118 118
119 119 # Build dict of handlers for message types
120 120 msg_types = [ 'execute_request', 'complete_request',
121 121 'inspect_request', 'history_request',
122 122 'kernel_info_request',
123 123 'connect_request', 'shutdown_request',
124 124 'apply_request', 'is_complete_request',
125 125 ]
126 126 self.shell_handlers = {}
127 127 for msg_type in msg_types:
128 128 self.shell_handlers[msg_type] = getattr(self, msg_type)
129 129
130 130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
131 131 self.control_handlers = {}
132 132 for msg_type in control_msg_types:
133 133 self.control_handlers[msg_type] = getattr(self, msg_type)
134 134
135 135
136 136 def dispatch_control(self, msg):
137 137 """dispatch control requests"""
138 138 idents,msg = self.session.feed_identities(msg, copy=False)
139 139 try:
140 140 msg = self.session.deserialize(msg, content=True, copy=False)
141 141 except:
142 142 self.log.error("Invalid Control Message", exc_info=True)
143 143 return
144 144
145 145 self.log.debug("Control received: %s", msg)
146 146
147 147 # Set the parent message for side effects.
148 148 self.set_parent(idents, msg)
149 149 self._publish_status(u'busy')
150 150
151 151 header = msg['header']
152 152 msg_type = header['msg_type']
153 153
154 154 handler = self.control_handlers.get(msg_type, None)
155 155 if handler is None:
156 156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
157 157 else:
158 158 try:
159 159 handler(self.control_stream, idents, msg)
160 160 except Exception:
161 161 self.log.error("Exception in control handler:", exc_info=True)
162 162
163 163 sys.stdout.flush()
164 164 sys.stderr.flush()
165 165 self._publish_status(u'idle')
166 166
167 167 def dispatch_shell(self, stream, msg):
168 168 """dispatch shell requests"""
169 169 # flush control requests first
170 170 if self.control_stream:
171 171 self.control_stream.flush()
172 172
173 173 idents,msg = self.session.feed_identities(msg, copy=False)
174 174 try:
175 175 msg = self.session.deserialize(msg, content=True, copy=False)
176 176 except:
177 177 self.log.error("Invalid Message", exc_info=True)
178 178 return
179 179
180 180 # Set the parent message for side effects.
181 181 self.set_parent(idents, msg)
182 182 self._publish_status(u'busy')
183 183
184 184 header = msg['header']
185 185 msg_id = header['msg_id']
186 186 msg_type = msg['header']['msg_type']
187 187
188 188 # Print some info about this message and leave a '--->' marker, so it's
189 189 # easier to trace visually the message chain when debugging. Each
190 190 # handler prints its message at the end.
191 191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
192 192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
193 193
194 194 if msg_id in self.aborted:
195 195 self.aborted.remove(msg_id)
196 196 # is it safe to assume a msg_id will not be resubmitted?
197 197 reply_type = msg_type.split('_')[0] + '_reply'
198 198 status = {'status' : 'aborted'}
199 199 md = {'engine' : self.ident}
200 200 md.update(status)
201 201 self.session.send(stream, reply_type, metadata=md,
202 202 content=status, parent=msg, ident=idents)
203 203 return
204 204
205 205 handler = self.shell_handlers.get(msg_type, None)
206 206 if handler is None:
207 207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
208 208 else:
209 209 # ensure default_int_handler during handler call
210 210 sig = signal(SIGINT, default_int_handler)
211 211 self.log.debug("%s: %s", msg_type, msg)
212 212 try:
213 213 handler(stream, idents, msg)
214 214 except Exception:
215 215 self.log.error("Exception in message handler:", exc_info=True)
216 216 finally:
217 217 signal(SIGINT, sig)
218 218
219 219 sys.stdout.flush()
220 220 sys.stderr.flush()
221 221 self._publish_status(u'idle')
222 222
223 223 def enter_eventloop(self):
224 224 """enter eventloop"""
225 225 self.log.info("entering eventloop %s", self.eventloop)
226 226 for stream in self.shell_streams:
227 227 # flush any pending replies,
228 228 # which may be skipped by entering the eventloop
229 229 stream.flush(zmq.POLLOUT)
230 230 # restore default_int_handler
231 231 signal(SIGINT, default_int_handler)
232 232 while self.eventloop is not None:
233 233 try:
234 234 self.eventloop(self)
235 235 except KeyboardInterrupt:
236 236 # Ctrl-C shouldn't crash the kernel
237 237 self.log.error("KeyboardInterrupt caught in kernel")
238 238 continue
239 239 else:
240 240 # eventloop exited cleanly, this means we should stop (right?)
241 241 self.eventloop = None
242 242 break
243 243 self.log.info("exiting eventloop")
244 244
245 245 def start(self):
246 246 """register dispatchers for streams"""
247 247 if self.control_stream:
248 248 self.control_stream.on_recv(self.dispatch_control, copy=False)
249 249
250 250 def make_dispatcher(stream):
251 251 def dispatcher(msg):
252 252 return self.dispatch_shell(stream, msg)
253 253 return dispatcher
254 254
255 255 for s in self.shell_streams:
256 256 s.on_recv(make_dispatcher(s), copy=False)
257 257
258 258 # publish idle status
259 259 self._publish_status('starting')
260 260
261 261 def do_one_iteration(self):
262 262 """step eventloop just once"""
263 263 if self.control_stream:
264 264 self.control_stream.flush()
265 265 for stream in self.shell_streams:
266 266 # handle at most one request per iteration
267 267 stream.flush(zmq.POLLIN, 1)
268 268 stream.flush(zmq.POLLOUT)
269 269
270 270
271 271 def record_ports(self, ports):
272 272 """Record the ports that this kernel is using.
273 273
274 274 The creator of the Kernel instance must call this methods if they
275 275 want the :meth:`connect_request` method to return the port numbers.
276 276 """
277 277 self._recorded_ports = ports
278 278
279 279 #---------------------------------------------------------------------------
280 280 # Kernel request handlers
281 281 #---------------------------------------------------------------------------
282 282
283 283 def _make_metadata(self, other=None):
284 284 """init metadata dict, for execute/apply_reply"""
285 285 new_md = {
286 286 'dependencies_met' : True,
287 287 'engine' : self.ident,
288 288 'started': datetime.now(),
289 289 }
290 290 if other:
291 291 new_md.update(other)
292 292 return new_md
293 293
294 294 def _publish_execute_input(self, code, parent, execution_count):
295 295 """Publish the code request on the iopub stream."""
296 296
297 297 self.session.send(self.iopub_socket, u'execute_input',
298 298 {u'code':code, u'execution_count': execution_count},
299 299 parent=parent, ident=self._topic('execute_input')
300 300 )
301 301
302 302 def _publish_status(self, status, parent=None):
303 303 """send status (busy/idle) on IOPub"""
304 304 self.session.send(self.iopub_socket,
305 305 u'status',
306 306 {u'execution_state': status},
307 307 parent=parent or self._parent_header,
308 308 ident=self._topic('status'),
309 309 )
310 310
311 311 def set_parent(self, ident, parent):
312 312 """Set the current parent_header
313 313
314 314 Side effects (IOPub messages) and replies are associated with
315 315 the request that caused them via the parent_header.
316 316
317 317 The parent identity is used to route input_request messages
318 318 on the stdin channel.
319 319 """
320 320 self._parent_ident = ident
321 321 self._parent_header = parent
322 322
323 323 def send_response(self, stream, msg_or_type, content=None, ident=None,
324 324 buffers=None, track=False, header=None, metadata=None):
325 325 """Send a response to the message we're currently processing.
326 326
327 327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
328 328 except ``parent``.
329 329
330 330 This relies on :meth:`set_parent` having been called for the current
331 331 message.
332 332 """
333 333 return self.session.send(stream, msg_or_type, content, self._parent_header,
334 334 ident, buffers, track, header, metadata)
335 335
336 336 def execute_request(self, stream, ident, parent):
337 337 """handle an execute_request"""
338 338
339 339 try:
340 340 content = parent[u'content']
341 341 code = py3compat.cast_unicode_py2(content[u'code'])
342 342 silent = content[u'silent']
343 343 store_history = content.get(u'store_history', not silent)
344 344 user_expressions = content.get('user_expressions', {})
345 345 allow_stdin = content.get('allow_stdin', False)
346 346 except:
347 347 self.log.error("Got bad msg: ")
348 348 self.log.error("%s", parent)
349 349 return
350 350
351 351 stop_on_error = content.get('stop_on_error', True)
352 352
353 353 md = self._make_metadata(parent['metadata'])
354 354
355 355 # Re-broadcast our input for the benefit of listening clients, and
356 356 # start computing output
357 357 if not silent:
358 358 self.execution_count += 1
359 359 self._publish_execute_input(code, parent, self.execution_count)
360 360
361 361 reply_content = self.do_execute(code, silent, store_history,
362 362 user_expressions, allow_stdin)
363 363
364 364 # Flush output before sending the reply.
365 365 sys.stdout.flush()
366 366 sys.stderr.flush()
367 367 # FIXME: on rare occasions, the flush doesn't seem to make it to the
368 368 # clients... This seems to mitigate the problem, but we definitely need
369 369 # to better understand what's going on.
370 370 if self._execute_sleep:
371 371 time.sleep(self._execute_sleep)
372 372
373 373 # Send the reply.
374 374 reply_content = json_clean(reply_content)
375 375
376 376 md['status'] = reply_content['status']
377 377 if reply_content['status'] == 'error' and \
378 378 reply_content['ename'] == 'UnmetDependency':
379 379 md['dependencies_met'] = False
380 380
381 381 reply_msg = self.session.send(stream, u'execute_reply',
382 382 reply_content, parent, metadata=md,
383 383 ident=ident)
384 384
385 385 self.log.debug("%s", reply_msg)
386 386
387 387 if not silent and reply_msg['content']['status'] == u'error' and stop_on_error:
388 388 self._abort_queues()
389 389
390 390 def do_execute(self, code, silent, store_history=True,
391 391 user_expressions=None, allow_stdin=False):
392 392 """Execute user code. Must be overridden by subclasses.
393 393 """
394 394 raise NotImplementedError
395 395
396 396 def complete_request(self, stream, ident, parent):
397 397 content = parent['content']
398 398 code = content['code']
399 399 cursor_pos = content['cursor_pos']
400 400
401 401 matches = self.do_complete(code, cursor_pos)
402 402 matches = json_clean(matches)
403 403 completion_msg = self.session.send(stream, 'complete_reply',
404 404 matches, parent, ident)
405 405 self.log.debug("%s", completion_msg)
406 406
407 407 def do_complete(self, code, cursor_pos):
408 408 """Override in subclasses to find completions.
409 409 """
410 410 return {'matches' : [],
411 411 'cursor_end' : cursor_pos,
412 412 'cursor_start' : cursor_pos,
413 413 'metadata' : {},
414 414 'status' : 'ok'}
415 415
416 416 def inspect_request(self, stream, ident, parent):
417 417 content = parent['content']
418 418
419 419 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
420 420 content.get('detail_level', 0))
421 421 # Before we send this object over, we scrub it for JSON usage
422 422 reply_content = json_clean(reply_content)
423 423 msg = self.session.send(stream, 'inspect_reply',
424 424 reply_content, parent, ident)
425 425 self.log.debug("%s", msg)
426 426
427 427 def do_inspect(self, code, cursor_pos, detail_level=0):
428 428 """Override in subclasses to allow introspection.
429 429 """
430 430 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
431 431
432 432 def history_request(self, stream, ident, parent):
433 433 content = parent['content']
434 434
435 435 reply_content = self.do_history(**content)
436 436
437 437 reply_content = json_clean(reply_content)
438 438 msg = self.session.send(stream, 'history_reply',
439 439 reply_content, parent, ident)
440 440 self.log.debug("%s", msg)
441 441
442 442 def do_history(self, hist_access_type, output, raw, session=None, start=None,
443 443 stop=None, n=None, pattern=None, unique=False):
444 444 """Override in subclasses to access history.
445 445 """
446 446 return {'history': []}
447 447
448 448 def connect_request(self, stream, ident, parent):
449 449 if self._recorded_ports is not None:
450 450 content = self._recorded_ports.copy()
451 451 else:
452 452 content = {}
453 453 msg = self.session.send(stream, 'connect_reply',
454 454 content, parent, ident)
455 455 self.log.debug("%s", msg)
456 456
457 457 @property
458 458 def kernel_info(self):
459 459 return {
460 460 'protocol_version': release.kernel_protocol_version,
461 461 'implementation': self.implementation,
462 462 'implementation_version': self.implementation_version,
463 463 'language_info': self.language_info,
464 464 'banner': self.banner,
465 465 'help_links': self.help_links,
466 466 }
467 467
468 468 def kernel_info_request(self, stream, ident, parent):
469 469 msg = self.session.send(stream, 'kernel_info_reply',
470 470 self.kernel_info, parent, ident)
471 471 self.log.debug("%s", msg)
472 472
473 473 def shutdown_request(self, stream, ident, parent):
474 474 content = self.do_shutdown(parent['content']['restart'])
475 475 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
476 476 # same content, but different msg_id for broadcasting on IOPub
477 477 self._shutdown_message = self.session.msg(u'shutdown_reply',
478 478 content, parent
479 479 )
480 480
481 481 self._at_shutdown()
482 482 # call sys.exit after a short delay
483 483 loop = ioloop.IOLoop.instance()
484 484 loop.add_timeout(time.time()+0.1, loop.stop)
485 485
486 486 def do_shutdown(self, restart):
487 487 """Override in subclasses to do things when the frontend shuts down the
488 488 kernel.
489 489 """
490 490 return {'status': 'ok', 'restart': restart}
491 491
492 492 def is_complete_request(self, stream, ident, parent):
493 493 content = parent['content']
494 494 code = content['code']
495 495
496 496 reply_content = self.do_is_complete(code)
497 497 reply_content = json_clean(reply_content)
498 498 reply_msg = self.session.send(stream, 'is_complete_reply',
499 499 reply_content, parent, ident)
500 500 self.log.debug("%s", reply_msg)
501 501
502 502 def do_is_complete(self, code):
503 503 """Override in subclasses to find completions.
504 504 """
505 505 return {'status' : 'unknown',
506 506 }
507 507
508 508 #---------------------------------------------------------------------------
509 509 # Engine methods
510 510 #---------------------------------------------------------------------------
511 511
512 512 def apply_request(self, stream, ident, parent):
513 513 try:
514 514 content = parent[u'content']
515 515 bufs = parent[u'buffers']
516 516 msg_id = parent['header']['msg_id']
517 517 except:
518 518 self.log.error("Got bad msg: %s", parent, exc_info=True)
519 519 return
520 520
521 521 md = self._make_metadata(parent['metadata'])
522 522
523 523 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
524 524
525 525 # put 'ok'/'error' status in header, for scheduler introspection:
526 526 md['status'] = reply_content['status']
527 527
528 528 # flush i/o
529 529 sys.stdout.flush()
530 530 sys.stderr.flush()
531 531
532 532 self.session.send(stream, u'apply_reply', reply_content,
533 533 parent=parent, ident=ident,buffers=result_buf, metadata=md)
534 534
535 535 def do_apply(self, content, bufs, msg_id, reply_metadata):
536 536 """Override in subclasses to support the IPython parallel framework.
537 537 """
538 538 raise NotImplementedError
539 539
540 540 #---------------------------------------------------------------------------
541 541 # Control messages
542 542 #---------------------------------------------------------------------------
543 543
544 544 def abort_request(self, stream, ident, parent):
545 545 """abort a specific msg by id"""
546 546 msg_ids = parent['content'].get('msg_ids', None)
547 547 if isinstance(msg_ids, string_types):
548 548 msg_ids = [msg_ids]
549 549 if not msg_ids:
550 550 self._abort_queues()
551 551 for mid in msg_ids:
552 552 self.aborted.add(str(mid))
553 553
554 554 content = dict(status='ok')
555 555 reply_msg = self.session.send(stream, 'abort_reply', content=content,
556 556 parent=parent, ident=ident)
557 557 self.log.debug("%s", reply_msg)
558 558
559 559 def clear_request(self, stream, idents, parent):
560 560 """Clear our namespace."""
561 561 content = self.do_clear()
562 562 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
563 563 content = content)
564 564
565 565 def do_clear(self):
566 566 """Override in subclasses to clear the namespace
567 567
568 568 This is only required for IPython.parallel.
569 569 """
570 570 raise NotImplementedError
571 571
572 572 #---------------------------------------------------------------------------
573 573 # Protected interface
574 574 #---------------------------------------------------------------------------
575 575
576 576 def _topic(self, topic):
577 577 """prefixed topic for IOPub messages"""
578 578 if self.int_id >= 0:
579 579 base = "engine.%i" % self.int_id
580 580 else:
581 581 base = "kernel.%s" % self.ident
582 582
583 583 return py3compat.cast_bytes("%s.%s" % (base, topic))
584 584
585 585 def _abort_queues(self):
586 586 for stream in self.shell_streams:
587 587 if stream:
588 588 self._abort_queue(stream)
589 589
590 590 def _abort_queue(self, stream):
591 591 poller = zmq.Poller()
592 592 poller.register(stream.socket, zmq.POLLIN)
593 593 while True:
594 594 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
595 595 if msg is None:
596 596 return
597 597
598 598 self.log.info("Aborting:")
599 599 self.log.info("%s", msg)
600 600 msg_type = msg['header']['msg_type']
601 601 reply_type = msg_type.split('_')[0] + '_reply'
602 602
603 603 status = {'status' : 'aborted'}
604 604 md = {'engine' : self.ident}
605 605 md.update(status)
606 606 reply_msg = self.session.send(stream, reply_type, metadata=md,
607 607 content=status, parent=msg, ident=idents)
608 608 self.log.debug("%s", reply_msg)
609 609 # We need to wait a bit for requests to come in. This can probably
610 610 # be set shorter for true asynchronous clients.
611 611 poller.poll(50)
612 612
613 613
614 614 def _no_raw_input(self):
615 615 """Raise StdinNotImplentedError if active frontend doesn't support
616 616 stdin."""
617 617 raise StdinNotImplementedError("raw_input was called, but this "
618 618 "frontend does not support stdin.")
619 619
620 620 def getpass(self, prompt=''):
621 621 """Forward getpass to frontends
622 622
623 623 Raises
624 624 ------
625 625 StdinNotImplentedError if active frontend doesn't support stdin.
626 626 """
627 627 if not self._allow_stdin:
628 628 raise StdinNotImplementedError(
629 629 "getpass was called, but this frontend does not support input requests."
630 630 )
631 631 return self._input_request(prompt,
632 632 self._parent_ident,
633 633 self._parent_header,
634 634 password=True,
635 635 )
636 636
637 637 def raw_input(self, prompt=''):
638 638 """Forward raw_input to frontends
639 639
640 640 Raises
641 641 ------
642 642 StdinNotImplentedError if active frontend doesn't support stdin.
643 643 """
644 644 if not self._allow_stdin:
645 645 raise StdinNotImplementedError(
646 646 "raw_input was called, but this frontend does not support input requests."
647 647 )
648 648 return self._input_request(prompt,
649 649 self._parent_ident,
650 650 self._parent_header,
651 651 password=False,
652 652 )
653 653
654 654 def _input_request(self, prompt, ident, parent, password=False):
655 655 # Flush output before making the request.
656 656 sys.stderr.flush()
657 657 sys.stdout.flush()
658 658 # flush the stdin socket, to purge stale replies
659 659 while True:
660 660 try:
661 661 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
662 662 except zmq.ZMQError as e:
663 663 if e.errno == zmq.EAGAIN:
664 664 break
665 665 else:
666 666 raise
667 667
668 668 # Send the input request.
669 669 content = json_clean(dict(prompt=prompt, password=password))
670 670 self.session.send(self.stdin_socket, u'input_request', content, parent,
671 671 ident=ident)
672 672
673 673 # Await a response.
674 674 while True:
675 675 try:
676 676 ident, reply = self.session.recv(self.stdin_socket, 0)
677 677 except Exception:
678 678 self.log.warn("Invalid Message:", exc_info=True)
679 679 except KeyboardInterrupt:
680 680 # re-raise KeyboardInterrupt, to truncate traceback
681 681 raise KeyboardInterrupt
682 682 else:
683 683 break
684 684 try:
685 685 value = py3compat.unicode_to_str(reply['content']['value'])
686 686 except:
687 687 self.log.error("Bad input_reply: %s", parent)
688 688 value = ''
689 689 if value == '\x04':
690 690 # EOF
691 691 raise EOFError
692 692 return value
693 693
694 694 def _at_shutdown(self):
695 695 """Actions taken at shutdown by the kernel, called by python's atexit.
696 696 """
697 697 # io.rprint("Kernel at_shutdown") # dbg
698 698 if self._shutdown_message is not None:
699 699 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
700 700 self.log.debug("%s", self._shutdown_message)
701 701 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now