# encoding: utf-8 """ A mixin for :class:`~IPython.core.application.Application` classes that launch InteractiveShell instances, load extensions, etc. """ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import absolute_import from __future__ import print_function import glob import os import sys from traitlets.config.application import boolean_flag from traitlets.config.configurable import Configurable from traitlets.config.loader import Config from IPython.core import pylabtools from IPython.utils import py3compat from IPython.utils.contexts import preserve_keys from IPython.utils.path import filefind from traitlets import ( Unicode, Instance, List, Bool, CaselessStrEnum ) from IPython.lib.inputhook import guis #----------------------------------------------------------------------------- # Aliases and Flags #----------------------------------------------------------------------------- gui_keys = tuple(sorted([ key for key in guis if key is not None ])) backend_keys = sorted(pylabtools.backends.keys()) backend_keys.insert(0, 'auto') shell_flags = {} addflag = lambda *args: shell_flags.update(boolean_flag(*args)) addflag('autoindent', 'InteractiveShell.autoindent', 'Turn on autoindenting.', 'Turn off autoindenting.' ) addflag('automagic', 'InteractiveShell.automagic', """Turn on the auto calling of magic commands. Type %%magic at the IPython prompt for more information.""", 'Turn off the auto calling of magic commands.' ) addflag('pdb', 'InteractiveShell.pdb', "Enable auto calling the pdb debugger after every exception.", "Disable auto calling the pdb debugger after every exception." ) # pydb flag doesn't do any config, as core.debugger switches on import, # which is before parsing. This just allows the flag to be passed. shell_flags.update(dict( pydb = ({}, """Use the third party 'pydb' package as debugger, instead of pdb. Requires that pydb is installed.""" ) )) addflag('pprint', 'PlainTextFormatter.pprint', "Enable auto pretty printing of results.", "Disable auto pretty printing of results." ) addflag('color-info', 'InteractiveShell.color_info', """IPython can display information about objects via a set of functions, and optionally can use colors for this, syntax highlighting source code and various other elements. This is on by default, but can cause problems with some pagers. If you see such problems, you can disable the colours.""", "Disable using colors for info related things." ) addflag('deep-reload', 'InteractiveShell.deep_reload', """Enable deep (recursive) reloading by default. IPython can use the deep_reload module which reloads changes in modules recursively (it replaces the reload() function, so you don't need to change anything to use it). deep_reload() forces a full reload of modules whose code may have changed, which the default reload() function does not. When deep_reload is off, IPython will use the normal reload(), but deep_reload will still be available as dreload(). This feature is off by default [which means that you have both normal reload() and dreload()].""", "Disable deep (recursive) reloading by default." ) nosep_config = Config() nosep_config.InteractiveShell.separate_in = '' nosep_config.InteractiveShell.separate_out = '' nosep_config.InteractiveShell.separate_out2 = '' shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.") shell_flags['pylab'] = ( {'InteractiveShellApp' : {'pylab' : 'auto'}}, """Pre-load matplotlib and numpy for interactive use with the default matplotlib backend.""" ) shell_flags['matplotlib'] = ( {'InteractiveShellApp' : {'matplotlib' : 'auto'}}, """Configure matplotlib for interactive use with the default matplotlib backend.""" ) # it's possible we don't want short aliases for *all* of these: shell_aliases = dict( autocall='InteractiveShell.autocall', colors='InteractiveShell.colors', logfile='InteractiveShell.logfile', logappend='InteractiveShell.logappend', c='InteractiveShellApp.code_to_run', m='InteractiveShellApp.module_to_run', ext='InteractiveShellApp.extra_extension', gui='InteractiveShellApp.gui', pylab='InteractiveShellApp.pylab', matplotlib='InteractiveShellApp.matplotlib', ) shell_aliases['cache-size'] = 'InteractiveShell.cache_size' #----------------------------------------------------------------------------- # Main classes and functions #----------------------------------------------------------------------------- class InteractiveShellApp(Configurable): """A Mixin for applications that start InteractiveShell instances. Provides configurables for loading extensions and executing files as part of configuring a Shell environment. The following methods should be called by the :meth:`initialize` method of the subclass: - :meth:`init_path` - :meth:`init_shell` (to be implemented by the subclass) - :meth:`init_gui_pylab` - :meth:`init_extensions` - :meth:`init_code` """ extensions = List(Unicode, config=True, help="A list of dotted module names of IPython extensions to load." ) extra_extension = Unicode('', config=True, help="dotted module name of an IPython extension to load." ) reraise_ipython_extension_failures = Bool( False, config=True, help="Reraise exceptions encountered loading IPython extensions?", ) # Extensions that are always loaded (not configurable) default_extensions = List(Unicode, [u'storemagic'], config=False) hide_initial_ns = Bool(True, config=True, help="""Should variables loaded at startup (by startup files, exec_lines, etc.) be hidden from tools like %who?""" ) exec_files = List(Unicode, config=True, help="""List of files to run at IPython startup.""" ) exec_PYTHONSTARTUP = Bool(True, config=True, help="""Run the file referenced by the PYTHONSTARTUP environment variable at IPython startup.""" ) file_to_run = Unicode('', config=True, help="""A file to be run""") exec_lines = List(Unicode, config=True, help="""lines of code to run at IPython startup.""" ) code_to_run = Unicode('', config=True, help="Execute the given command string." ) module_to_run = Unicode('', config=True, help="Run the module as a script." ) gui = CaselessStrEnum(gui_keys, config=True, allow_none=True, help="Enable GUI event loop integration with any of {0}.".format(gui_keys) ) matplotlib = CaselessStrEnum(backend_keys, allow_none=True, config=True, help="""Configure matplotlib for interactive use with the default matplotlib backend.""" ) pylab = CaselessStrEnum(backend_keys, allow_none=True, config=True, help="""Pre-load matplotlib and numpy for interactive use, selecting a particular matplotlib backend and loop integration. """ ) pylab_import_all = Bool(True, config=True, help="""If true, IPython will populate the user namespace with numpy, pylab, etc. and an ``import *`` is done from numpy and pylab, when using pylab mode. When False, pylab mode should not import any names into the user namespace. """ ) shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) user_ns = Instance(dict, args=None, allow_none=True) def _user_ns_changed(self, name, old, new): if self.shell is not None: self.shell.user_ns = new self.shell.init_user_ns() def init_path(self): """Add current working directory, '', to sys.path""" if sys.path[0] != '': sys.path.insert(0, '') def init_shell(self): raise NotImplementedError("Override in subclasses") def init_gui_pylab(self): """Enable GUI event loop integration, taking pylab into account.""" enable = False shell = self.shell if self.pylab: enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all) key = self.pylab elif self.matplotlib: enable = shell.enable_matplotlib key = self.matplotlib elif self.gui: enable = shell.enable_gui key = self.gui if not enable: return try: r = enable(key) except ImportError: self.log.warn("Eventloop or matplotlib integration failed. Is matplotlib installed?") self.shell.showtraceback() return except Exception: self.log.warn("GUI event loop or pylab initialization failed") self.shell.showtraceback() return if isinstance(r, tuple): gui, backend = r[:2] self.log.info("Enabling GUI event loop integration, " "eventloop=%s, matplotlib=%s", gui, backend) if key == "auto": print("Using matplotlib backend: %s" % backend) else: gui = r self.log.info("Enabling GUI event loop integration, " "eventloop=%s", gui) def init_extensions(self): """Load all IPython extensions in IPythonApp.extensions. This uses the :meth:`ExtensionManager.load_extensions` to load all the extensions listed in ``self.extensions``. """ try: self.log.debug("Loading IPython extensions...") extensions = self.default_extensions + self.extensions if self.extra_extension: extensions.append(self.extra_extension) for ext in extensions: try: self.log.info("Loading IPython extension: %s" % ext) self.shell.extension_manager.load_extension(ext) except: if self.reraise_ipython_extension_failures: raise msg = ("Error in loading extension: {ext}\n" "Check your config files in {location}".format( ext=ext, location=self.profile_dir.location )) self.log.warn(msg, exc_info=True) except: if self.reraise_ipython_extension_failures: raise self.log.warn("Unknown error in loading extensions:", exc_info=True) def init_code(self): """run the pre-flight code, specified via exec_lines""" self._run_startup_files() self._run_exec_lines() self._run_exec_files() # Hide variables defined here from %who etc. if self.hide_initial_ns: self.shell.user_ns_hidden.update(self.shell.user_ns) # command-line execution (ipython -i script.py, ipython -m module) # should *not* be excluded from %whos self._run_cmd_line_code() self._run_module() # flush output, so itwon't be attached to the first cell sys.stdout.flush() sys.stderr.flush() def _run_exec_lines(self): """Run lines of code in IPythonApp.exec_lines in the user's namespace.""" if not self.exec_lines: return try: self.log.debug("Running code from IPythonApp.exec_lines...") for line in self.exec_lines: try: self.log.info("Running code in user namespace: %s" % line) self.shell.run_cell(line, store_history=False) except: self.log.warn("Error in executing line in user " "namespace: %s" % line) self.shell.showtraceback() except: self.log.warn("Unknown error in handling IPythonApp.exec_lines:") self.shell.showtraceback() def _exec_file(self, fname, shell_futures=False): try: full_filename = filefind(fname, [u'.', self.ipython_dir]) except IOError as e: self.log.warn("File not found: %r"%fname) return # Make sure that the running script gets a proper sys.argv as if it # were run from a system shell. save_argv = sys.argv sys.argv = [full_filename] + self.extra_args[1:] # protect sys.argv from potential unicode strings on Python 2: if not py3compat.PY3: sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ] try: if os.path.isfile(full_filename): self.log.info("Running file in user namespace: %s" % full_filename) # Ensure that __file__ is always defined to match Python # behavior. with preserve_keys(self.shell.user_ns, '__file__'): self.shell.user_ns['__file__'] = fname if full_filename.endswith('.ipy'): self.shell.safe_execfile_ipy(full_filename, shell_futures=shell_futures) else: # default to python, even without extension self.shell.safe_execfile(full_filename, self.shell.user_ns, shell_futures=shell_futures) finally: sys.argv = save_argv def _run_startup_files(self): """Run files from profile startup directory""" startup_dir = self.profile_dir.startup_dir startup_files = [] if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \ not (self.file_to_run or self.code_to_run or self.module_to_run): python_startup = os.environ['PYTHONSTARTUP'] self.log.debug("Running PYTHONSTARTUP file %s...", python_startup) try: self._exec_file(python_startup) except: self.log.warn("Unknown error in handling PYTHONSTARTUP file %s:", python_startup) self.shell.showtraceback() finally: # Many PYTHONSTARTUP files set up the readline completions, # but this is often at odds with IPython's own completions. # Do not allow PYTHONSTARTUP to set up readline. if self.shell.has_readline: self.shell.set_readline_completer() startup_files += glob.glob(os.path.join(startup_dir, '*.py')) startup_files += glob.glob(os.path.join(startup_dir, '*.ipy')) if not startup_files: return self.log.debug("Running startup files from %s...", startup_dir) try: for fname in sorted(startup_files): self._exec_file(fname) except: self.log.warn("Unknown error in handling startup files:") self.shell.showtraceback() def _run_exec_files(self): """Run files from IPythonApp.exec_files""" if not self.exec_files: return self.log.debug("Running files in IPythonApp.exec_files...") try: for fname in self.exec_files: self._exec_file(fname) except: self.log.warn("Unknown error in handling IPythonApp.exec_files:") self.shell.showtraceback() def _run_cmd_line_code(self): """Run code or file specified at the command-line""" if self.code_to_run: line = self.code_to_run try: self.log.info("Running code given at command line (c=): %s" % line) self.shell.run_cell(line, store_history=False) except: self.log.warn("Error in executing line in user namespace: %s" % line) self.shell.showtraceback() # Like Python itself, ignore the second if the first of these is present elif self.file_to_run: fname = self.file_to_run try: self._exec_file(fname, shell_futures=True) except: self.log.warn("Error in executing file in user namespace: %s" % fname) self.shell.showtraceback() def _run_module(self): """Run module specified at the command-line.""" if self.module_to_run: # Make sure that the module gets a proper sys.argv as if it were # run using `python -m`. save_argv = sys.argv sys.argv = [sys.executable] + self.extra_args try: self.shell.safe_run_module(self.module_to_run, self.shell.user_ns) finally: sys.argv = save_argv