From 3ddcb2dc9b68a5a389b12e83eb625fc8fa9fdbab 2009-09-13 22:06:04 From: Brian Granger Date: 2009-09-13 22:06:04 Subject: [PATCH] Work on startup related things. * Extension loading works. * Execution of code from user config file works. * Reworked logging in application.py and ipapp.py to use logging. * Updated config files. * Changed how display_banner is handled and implemented show_banner. * Implemented safe_execfile_ipy to run .ipy files using runlines. --- diff --git a/IPython/config/profile/ipython_config_math.py b/IPython/config/profile/ipython_config_math.py index f18448e..2238227 100644 --- a/IPython/config/profile/ipython_config_math.py +++ b/IPython/config/profile/ipython_config_math.py @@ -1,9 +1,8 @@ from ipython_config import * -EXECUTE.extend([ +Global.exec_lines.extend([ 'import cmath', 'from math import *', 'print "*** math functions available globally, cmath as a module"' - ]) diff --git a/IPython/config/profile/ipython_config_numeric.py b/IPython/config/profile/ipython_config_numeric.py index 70a731e..28031fa 100644 --- a/IPython/config/profile/ipython_config_numeric.py +++ b/IPython/config/profile/ipython_config_numeric.py @@ -1,6 +1,6 @@ from ipython_config import * -EXECUTE.extend([ +Global.exec_lines.extend([ 'import numpy', 'import scipy', 'import numpy as np', diff --git a/IPython/config/profile/ipython_config_pylab.py b/IPython/config/profile/ipython_config_pylab.py index 705b665..edf6c5d 100644 --- a/IPython/config/profile/ipython_config_pylab.py +++ b/IPython/config/profile/ipython_config_pylab.py @@ -1,7 +1,8 @@ from ipython_config_numeric import * -EXECUTE.extend([ - - +Global.exec_lines.extend([ + 'import matplotlib', + 'from matplotlib import pyplot as plt', + 'from matplotlib.pyplot import *' ]) diff --git a/IPython/config/profile/ipython_config_pysh.py b/IPython/config/profile/ipython_config_pysh.py index 1720b87..3fe3f95 100644 --- a/IPython/config/profile/ipython_config_pysh.py +++ b/IPython/config/profile/ipython_config_pysh.py @@ -1,15 +1,13 @@ from ipython_config import * -EXECUTE.insert(0, 'from IPython.extensions.InterpreterExec import *') +InteractiveShell.prompt_in2 = '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> ' +InteractiveShell.prompt_in2 = '\C_Green|\C_LightGreen\D\C_Green> ' +InteractiveShell.prompt_out = '<\#> ' -PROMPT_IN1 = '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> ' -PROMPT_IN2 = '\C_Green|\C_LightGreen\D\C_Green> ' -PROMPT_OUT = '<\#> ' +InteractiveShell.prompts_pad_left = True -PROMPTS_PAD_LEFT = True +InteractiveShell.separate_in = '' +InteractiveShell.separate_out = '' +InteractiveShell.separate_out2 = '' -SEPARATE_IN = 0 -SEPARATE_OUT = 0 -SEPARATE_OUT2 = 0 - -MULTI_LINE_SPECIALS = True \ No newline at end of file +PrefilterManager.multi_line_specials = True \ No newline at end of file diff --git a/IPython/config/profile/ipython_config_sympy.py b/IPython/config/profile/ipython_config_sympy.py index a36b833..2bf5bef 100644 --- a/IPython/config/profile/ipython_config_sympy.py +++ b/IPython/config/profile/ipython_config_sympy.py @@ -1,9 +1,9 @@ from ipython_config import * -EXECUTE.extend([ - 'from __future__ import division' - 'from sympy import *' - 'x, y, z = symbols('xyz')' - 'k, m, n = symbols('kmn', integer=True)' - 'f, g, h = map(Function, 'fgh')' +Global.exec_lines.extend([ + "from __future__ import division" + "from sympy import *" + "x, y, z = symbols('xyz')" + "k, m, n = symbols('kmn', integer=True)" + "f, g, h = map(Function, 'fgh')" ]) diff --git a/IPython/core/alias.py b/IPython/core/alias.py index 60cabc1..3dbb8cd 100644 --- a/IPython/core/alias.py +++ b/IPython/core/alias.py @@ -112,11 +112,9 @@ class AliasManager(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] def __contains__(self, name): if name in self.alias_table: diff --git a/IPython/core/application.py b/IPython/core/application.py index 882064b..90b2f40 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -23,6 +23,7 @@ Notes # Imports #----------------------------------------------------------------------------- +import logging import os import sys import traceback @@ -53,8 +54,8 @@ class IPythonArgParseConfigLoader(ArgParseConfigLoader): help='The string name of the ipython profile to be used.', default=NoConfigDefault, metavar='Global.profile') - self.parser.add_argument('-debug',dest="Global.debug",action='store_true', - help='Debug the application startup process.', + self.parser.add_argument('-log_level',dest="Global.log_level",type=int, + help='Set the log level (0,10,20,30,40,50). Default is 30.', default=NoConfigDefault) self.parser.add_argument('-config_file',dest='Global.config_file',type=str, help='Set the config file name to override default.', @@ -72,10 +73,26 @@ class Application(object): config_file_name = 'ipython_config.py' name = 'ipython' - debug = False def __init__(self): - pass + self.init_logger() + + def init_logger(self): + self.log = logging.getLogger(self.__class__.__name__) + # This is used as the default until the command line arguments are read. + self.log.setLevel(logging.WARN) + self._log_handler = logging.StreamHandler() + self._log_formatter = logging.Formatter("[%(name)s] %(message)s") + self._log_handler.setFormatter(self._log_formatter) + self.log.addHandler(self._log_handler) + + def _set_log_level(self, level): + self.log.setLevel(level) + + def _get_log_level(self): + return self.log.level + + log_level = property(_get_log_level, _set_log_level) def start(self): """Start the application.""" @@ -103,6 +120,8 @@ class Application(object): """Create defaults that can't be set elsewhere.""" self.default_config = Config() self.default_config.Global.ipythondir = get_ipython_dir() + self.log.debug('Default config loaded:') + self.log.debug(repr(self.default_config)) def create_command_line_config(self): """Create and return a command line config loader.""" @@ -120,12 +139,14 @@ class Application(object): loader = self.create_command_line_config() self.command_line_config = loader.load_config() + try: - self.debug = self.command_line_config.Global.debug + self.log_level = self.command_line_config.Global.log_level except AttributeError: - pass # use class default - self.log("Default config loaded:", self.default_config) - self.log("Command line config loaded:", self.command_line_config) + pass # Use existing value which is set in Application.init_logger. + + self.log.debug("Command line config loaded:") + self.log.debug(repr(self.command_line_config)) def post_load_command_line_config(self): """Do actions just after loading the command line config.""" @@ -148,7 +169,7 @@ class Application(object): sys.path.append(os.path.abspath(self.ipythondir)) if not os.path.isdir(self.ipythondir): os.makedirs(self.ipythondir, mode = 0777) - self.log("IPYTHONDIR set to: %s" % self.ipythondir) + self.log.debug("IPYTHONDIR set to: %s" % self.ipythondir) def find_config_file_name(self): """Find the config file name for this application. @@ -187,18 +208,28 @@ class Application(object): ``CONFIG_FILE`` config variable is set to the resolved config file location. If not successful, an empty config is used. """ + self.log.debug("Attempting to load config file: <%s>" % self.config_file_name) loader = PyFileConfigLoader(self.config_file_name, path=self.config_file_paths) try: self.file_config = loader.load_config() self.file_config.Global.config_file = loader.full_filename except IOError: - self.log("Config file not found, skipping: %s" % \ - self.config_file_name) + self.log.warn("Config file not found, skipping: <%s>" % \ + self.config_file_name, exc_info=True) + self.file_config = Config() + except: + self.log.warn("Error loading config file: <%s>" % \ + self.config_file_name, exc_info=True) self.file_config = Config() else: - self.log("Config file loaded: %s" % loader.full_filename, - self.file_config) + self.log.debug("Config file loaded: <%s>" % loader.full_filename) + self.log.debug(repr(self.file_config)) + # We need to keeep self.log_level updated. + try: + self.log_level = self.file_config.Global.log_level + except AttributeError: + pass # Use existing value def post_load_file_config(self): """Do actions after the config file is loaded.""" @@ -211,7 +242,8 @@ class Application(object): config._merge(self.file_config) config._merge(self.command_line_config) self.master_config = config - self.log("Master config created:", self.master_config) + self.log.debug("Master config created:") + self.log.debug(repr(self.master_config)) def pre_construct(self): """Do actions after the config has been built, but before construct.""" @@ -219,7 +251,7 @@ class Application(object): def construct(self): """Construct the main components that make up this app.""" - self.log("Constructing components for application...") + self.log.debug("Constructing components for application") def post_construct(self): """Do actions after construct, but before starting the app.""" @@ -227,7 +259,7 @@ class Application(object): def start_app(self): """Actually start the app.""" - self.log("Starting application...") + self.log.debug("Starting application") #------------------------------------------------------------------------- # Utility methods @@ -235,11 +267,11 @@ class Application(object): def abort(self): """Abort the starting of the application.""" - print "Aborting application: ", self.name + self.log.critical("Aborting application: %s" % self.name, exc_info=True) sys.exit(1) def exit(self): - print "Exiting application: ", self.name + self.log.critical("Aborting application: %s" % self.name) sys.exit(1) def attempt(self, func, action='abort'): @@ -249,17 +281,7 @@ class Application(object): self.exit() except: if action == 'abort': - self.print_traceback() self.abort() elif action == 'exit': self.exit() - - def print_traceback(self): - print "Error in appliction startup: ", self.name - print - traceback.print_exc() - - def log(self, *args): - if self.debug: - for arg in args: - print "[%s] %s" % (self.name, arg) \ No newline at end of file + \ No newline at end of file diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py index a05fc49..f528dbb 100644 --- a/IPython/core/builtin_trap.py +++ b/IPython/core/builtin_trap.py @@ -46,11 +46,9 @@ class BuiltinTrap(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] def __enter__(self): if self._nested_level == 0: diff --git a/IPython/core/display_trap.py b/IPython/core/display_trap.py index ff41a81..2f9fc3b 100644 --- a/IPython/core/display_trap.py +++ b/IPython/core/display_trap.py @@ -48,11 +48,9 @@ class DisplayTrap(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] def __enter__(self): if self._nested_level == 0: diff --git a/IPython/core/embed.py b/IPython/core/embed.py index bed686e..421c30c 100644 --- a/IPython/core/embed.py +++ b/IPython/core/embed.py @@ -64,10 +64,13 @@ class InteractiveShellEmbed(InteractiveShell): exit_msg = Str('') embedded = CBool(True) embedded_active = CBool(True) + # Like the base class display_banner is not configurable, but here it + # is True by default. + display_banner = CBool(True) def __init__(self, parent=None, config=None, ipythondir=None, usage=None, user_ns=None, user_global_ns=None, - banner1=None, banner2=None, + banner1=None, banner2=None, display_banner=None, custom_exceptions=((),None), exit_msg=''): self.save_sys_ipcompleter() @@ -75,7 +78,7 @@ class InteractiveShellEmbed(InteractiveShell): super(InteractiveShellEmbed,self).__init__( parent=parent, config=config, ipythondir=ipythondir, usage=usage, user_ns=user_ns, user_global_ns=user_global_ns, - banner1=banner1, banner2=banner2, + banner1=banner1, banner2=banner2, display_banner=display_banner, custom_exceptions=custom_exceptions) self.exit_msg = exit_msg @@ -148,23 +151,24 @@ class InteractiveShellEmbed(InteractiveShell): if self.has_readline: self.set_completer() - if self.banner and header: - format = '%s\n%s\n' - else: - format = '%s%s\n' - banner = format % (self.banner,header) + # self.banner is auto computed + if header: + self.old_banner2 = self.banner2 + self.banner2 = self.banner2 + '\n' + header + '\n' # Call the embedding code with a stack depth of 1 so it can skip over # our call and get the original caller's namespaces. - self.mainloop(banner, local_ns, global_ns, - stack_depth=stack_depth) + self.mainloop(local_ns, global_ns, stack_depth=stack_depth) + + self.banner2 = self.old_banner2 if self.exit_msg is not None: print self.exit_msg self.restore_sys_ipcompleter() - def mainloop(self,header='',local_ns=None,global_ns=None,stack_depth=0): + def mainloop(self, local_ns=None, global_ns=None, stack_depth=0, + display_banner=None): """Embeds IPython into a running python program. Input: @@ -221,7 +225,7 @@ class InteractiveShellEmbed(InteractiveShell): self.set_completer_frame() with nested(self.builtin_trap, self.display_trap): - self.interact(header) + self.interact(display_banner=display_banner) # now, purge out the user namespace from anything we might have added # from the caller's local namespace @@ -242,7 +246,7 @@ _embedded_shell = None def embed(header='', config=None, usage=None, banner1=None, banner2=None, - exit_msg=''): + display_banner=True, exit_msg=''): """Call this to embed IPython at the current point in your program. The first invocation of this will create an :class:`InteractiveShellEmbed` @@ -264,9 +268,13 @@ def embed(header='', config=None, usage=None, banner1=None, banner2=None, """ if config is None: config = load_default_config() + config.InteractiveShellEmbed = config.InteractiveShell global _embedded_shell if _embedded_shell is None: - _embedded_shell = InteractiveShellEmbed(config=config, - usage=usage, banner1=banner1, banner2=banner2, exit_msg=exit_msg) + _embedded_shell = InteractiveShellEmbed( + config=config, usage=usage, + banner1=banner1, banner2=banner2, + display_banner=display_banner, exit_msg=exit_msg + ) _embedded_shell(header=header, stack_depth=2) diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py index 2c4ace6..a61abcf 100644 --- a/IPython/core/ipapp.py +++ b/IPython/core/ipapp.py @@ -23,6 +23,7 @@ Notes # Imports #----------------------------------------------------------------------------- +import logging import os import sys import warnings @@ -33,11 +34,12 @@ from IPython.core.iplib import InteractiveShell from IPython.config.loader import ( NoConfigDefault, Config, + ConfigError, PyFileConfigLoader ) from IPython.utils.ipstruct import Struct -from IPython.utils.genutils import get_ipython_dir +from IPython.utils.genutils import filefind, get_ipython_dir #----------------------------------------------------------------------------- # Utilities and helpers @@ -95,11 +97,11 @@ cl_args = ( help='Turn off auto editing of files with syntax errors.') ), (('-banner',), dict( - action='store_true', dest='InteractiveShell.display_banner', default=NoConfigDefault, + action='store_true', dest='Global.display_banner', default=NoConfigDefault, help='Display a banner upon starting IPython.') ), (('-nobanner',), dict( - action='store_false', dest='InteractiveShell.display_banner', default=NoConfigDefault, + action='store_false', dest='Global.display_banner', default=NoConfigDefault, help="Don't display a banner upon starting IPython.") ), (('-c',), dict( @@ -244,6 +246,11 @@ cl_args = ( help="Exception mode ('Plain','Context','Verbose')", metavar='InteractiveShell.xmode') ), + (('-ext',), dict( + type=str, dest='Global.extra_extension', default=NoConfigDefault, + help="The dotted module name of an IPython extension to load.", + metavar='Global.extra_extension') + ), # These are only here to get the proper deprecation warnings (('-pylab','-wthread','-qthread','-q4thread','-gthread'), dict( action='store_true', dest='Global.threaded_shell', default=NoConfigDefault, @@ -263,6 +270,14 @@ class IPythonApp(Application): name = 'ipython' config_file_name = _default_config_file_name + def create_default_config(self): + super(IPythonApp, self).create_default_config() + self.default_config.Global.display_banner=True + # Let the parent class set the default, but each time log_level + # changes from config, we need to update self.log_level as that is + # what updates the actual log level in self.log. + self.default_config.Global.log_level = self.log_level + def create_command_line_config(self): """Create and return a command line config loader.""" return IPythonAppCLConfigLoader( @@ -286,7 +301,12 @@ class IPythonApp(Application): super(IPythonApp, self).load_file_config() def post_load_file_config(self): - """Logic goes here.""" + if hasattr(self.command_line_config.Global, 'extra_extension'): + if not hasattr(self.file_config.Global, 'extensions'): + self.file_config.Global.extensions = [] + self.file_config.Global.extensions.append( + self.command_line_config.Global.extra_extension) + del self.command_line_config.Global.extra_extension def pre_construct(self): config = self.master_config @@ -318,17 +338,90 @@ class IPythonApp(Application): # I am a little hesitant to put these into InteractiveShell itself. # But that might be the place for them sys.path.insert(0, '') - # add personal ipythondir to sys.path so that users can put things in - # there for customization - sys.path.append(os.path.abspath(self.ipythondir)) - + # Create an InteractiveShell instance self.shell = InteractiveShell( parent=None, config=self.master_config ) - + + def post_construct(self): + """Do actions after construct, but before starting the app.""" + # shell.display_banner should always be False for the terminal + # based app, because we call shell.show_banner() by hand below + # so the banner shows *before* all extension loading stuff. + self.shell.display_banner = False + + if self.master_config.Global.display_banner: + self.shell.show_banner() + + # Make sure there is a space below the banner. + if self.log_level <= logging.INFO: print + + self._load_extensions() + self._run_exec_lines() + self._run_exec_files() + + def _load_extensions(self): + """Load all IPython extensions in Global.extensions. + + This uses the :meth:`InteractiveShell.load_extensions` to load all + the extensions listed in ``self.master_config.Global.extensions``. + """ + try: + if hasattr(self.master_config.Global, 'extensions'): + self.log.debug("Loading IPython extensions...") + extensions = self.master_config.Global.extensions + for ext in extensions: + try: + self.log.info("Loading IPython extension: %s" % ext) + self.shell.load_extension(ext) + except: + self.log.warn("Error in loading extension: %s" % ext) + self.shell.showtraceback() + except: + self.log.warn("Unknown error in loading extensions:") + self.shell.showtraceback() + + def _run_exec_lines(self): + """Run lines of code in Global.exec_lines in the user's namespace.""" + try: + if hasattr(self.master_config.Global, 'exec_lines'): + self.log.debug("Running code from Global.exec_lines...") + exec_lines = self.master_config.Global.exec_lines + for line in exec_lines: + try: + self.log.info("Running code in user namespace: %s" % line) + self.shell.runlines(line) + except: + self.log.warn("Error in executing line in user namespace: %s" % line) + self.shell.showtraceback() + except: + self.log.warn("Unknown error in handling Global.exec_lines:") + self.shell.showtraceback() + + def _run_exec_files(self): + try: + if hasattr(self.master_config.Global, 'exec_files'): + self.log.debug("Running files in Global.exec_files...") + exec_files = self.master_config.Global.exec_files + for fname in exec_files: + full_filename = filefind(fname, ['.', self.ipythondir]) + if os.path.isfile(full_filename): + if full_filename.endswith('.py'): + self.log.info("Running file in user namespace: %s" % full_filename) + self.shell.safe_execfile(full_filename, self.shell.user_ns) + elif full_filename.endswith('.ipy'): + self.log.info("Running file in user namespace: %s" % full_filename) + self.shell.safe_execfile_ipy(full_filename) + else: + self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename) + except: + self.log.warn("Unknown error in handling Global.exec_files:") + self.shell.showtraceback() + def start_app(self): + self.log.debug("Starting IPython's mainloop...") self.shell.mainloop() diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py index 811c958..58c526f 100644 --- a/IPython/core/iplib.py +++ b/IPython/core/iplib.py @@ -55,8 +55,9 @@ from IPython.utils.ipstruct import Struct from IPython.utils import PyColorize from IPython.utils.genutils import * from IPython.utils.genutils import get_ipython_dir -from IPython.utils.strdispatch import StrDispatch from IPython.utils.platutils import toggle_set_term_title, set_term_title +from IPython.utils.strdispatch import StrDispatch +from IPython.utils.syspathcontext import prepended_to_syspath # from IPython.utils import growl # growl.start("IPython") @@ -185,7 +186,6 @@ class InteractiveShell(Component, Magic): autoedit_syntax = CBool(False, config=True) autoindent = CBool(True, config=True) automagic = CBool(True, config=True) - display_banner = CBool(True, config=True) banner = Str('') banner1 = Str(default_banner, config=True) banner2 = Str('', config=True) @@ -197,6 +197,12 @@ class InteractiveShell(Component, Magic): confirm_exit = CBool(True, config=True) debug = CBool(False, config=True) deep_reload = CBool(False, config=True) + # This display_banner only controls whether or not self.show_banner() + # is called when mainloop/interact are called. The default is False + # because for the terminal based application, the banner behavior + # is controlled by Global.display_banner, which IPythonApp looks at + # to determine if *it* should call show_banner() by hand or not. + display_banner = CBool(False) # This isn't configurable! embedded = CBool(False) embedded_active = CBool(False) editor = Str(get_default_editor(), config=True) @@ -262,7 +268,7 @@ class InteractiveShell(Component, Magic): def __init__(self, parent=None, config=None, ipythondir=None, usage=None, user_ns=None, user_global_ns=None, - banner1=None, banner2=None, + banner1=None, banner2=None, display_banner=None, custom_exceptions=((),None)): # This is where traitlets with a config_key argument are updated @@ -274,7 +280,7 @@ class InteractiveShell(Component, Magic): self.init_instance_attrs() self.init_term_title() self.init_usage(usage) - self.init_banner(banner1, banner2) + self.init_banner(banner1, banner2, display_banner) # Create namespaces (user_ns, user_global_ns, etc.) self.init_create_namespaces(user_ns, user_global_ns) @@ -331,6 +337,12 @@ class InteractiveShell(Component, Magic): def _ipythondir_changed(self, name, new): if not os.path.isdir(new): os.makedirs(new, mode = 0777) + if not os.path.isdir(self.ipython_extension_dir): + os.makedirs(self.ipython_extension_dir, mode = 0777) + + @property + def ipython_extension_dir(self): + return os.path.join(self.ipythondir, 'extensions') @property def usable_screen_length(self): @@ -429,22 +441,6 @@ class InteractiveShell(Component, Magic): else: self.usage = usage - def init_banner(self, banner1, banner2): - if self.c: # regular python doesn't print the banner with -c - self.display_banner = False - if banner1 is not None: - self.banner1 = banner1 - if banner2 is not None: - self.banner2 = banner2 - self.compute_banner() - - def compute_banner(self): - self.banner = self.banner1 + '\n' - if self.profile: - self.banner += '\nIPython profile: %s\n' % self.profile - if self.banner2: - self.banner += '\n' + self.banner2 + '\n' - def init_encoding(self): # Get system encoding at startup time. Certain terminals (like Emacs # under Win32 have it set to None, and we need to have a known valid @@ -486,7 +482,7 @@ class InteractiveShell(Component, Magic): def init_logstart(self): if self.logplay: self.magic_logstart(self.logplay + ' append') - elif self.logfile: + elif self.logfile: self.magic_logstart(self.logfile) elif self.logstart: self.magic_logstart() @@ -532,6 +528,31 @@ class InteractiveShell(Component, Magic): warn("doctest module does not exist.") #------------------------------------------------------------------------- + # Things related to the banner + #------------------------------------------------------------------------- + + def init_banner(self, banner1, banner2, display_banner): + if banner1 is not None: + self.banner1 = banner1 + if banner2 is not None: + self.banner2 = banner2 + if display_banner is not None: + self.display_banner = display_banner + self.compute_banner() + + def show_banner(self, banner=None): + if banner is None: + banner = self.banner + self.write(banner) + + def compute_banner(self): + self.banner = self.banner1 + '\n' + if self.profile: + self.banner += '\nIPython profile: %s\n' % self.profile + if self.banner2: + self.banner += '\n' + self.banner2 + '\n' + + #------------------------------------------------------------------------- # Things related to injections into the sys module #------------------------------------------------------------------------- @@ -1673,7 +1694,7 @@ class InteractiveShell(Component, Magic): with nested(self.builtin_trap,): return eval(expr, self.user_global_ns, self.user_ns) - def mainloop(self, banner=None): + def mainloop(self, display_banner=None): """Start the mainloop. If an optional banner argument is given, it will override the @@ -1684,10 +1705,6 @@ class InteractiveShell(Component, Magic): if self.c: # Emulate Python's -c option self.exec_init_cmd() - if self.display_banner: - if banner is None: - banner = self.banner - # if you run stuff with -c , raw hist is not updated # ensure that it's in sync if len(self.input_hist) != len (self.input_hist_raw): @@ -1695,7 +1712,7 @@ class InteractiveShell(Component, Magic): while 1: try: - self.interact() + self.interact(display_banner=display_banner) #self.interact_with_readline() # XXX for testing of a readline-decoupled repl loop, call # interact_with_readline above @@ -1774,17 +1791,17 @@ class InteractiveShell(Component, Magic): line = raw_input_original().decode(self.stdin_encoding) self.interact_handle_input(line) - def interact(self, banner=None): + def interact(self, display_banner=None): """Closely emulate the interactive Python console.""" # batch run -> do not interact if self.exit_now: return - if self.display_banner: - if banner is None: - banner = self.banner - self.write(banner) + if display_banner is None: + display_banner = self.display_banner + if display_banner: + self.show_banner() more = 0 @@ -1856,128 +1873,45 @@ class InteractiveShell(Component, Magic): # We are off again... __builtin__.__dict__['__IPYTHON__active'] -= 1 - def safe_execfile(self,fname,*where,**kw): + def safe_execfile(self, fname, *where, **kw): """A safe version of the builtin execfile(). - This version will never throw an exception, and knows how to handle - ipython logs as well. + This version will never throw an exception, but instead print + helpful error messages to the screen. This only works on pure + Python files with the .py extension. - :Parameters: - fname : string - Name of the file to be executed. - - where : tuple + Parameters + ---------- + fname : string + The name of the file to be executed. + where : tuple One or two namespaces, passed to execfile() as (globals,locals). If only one is given, it is passed as both. + exit_ignore : bool (False) + If True, then don't print errors for non-zero exit statuses. + """ + kw.setdefault('exit_ignore', False) - :Keywords: - islog : boolean (False) - - quiet : boolean (True) + fname = os.path.abspath(os.path.expanduser(fname)) - exit_ignore : boolean (False) - """ + # Make sure we have a .py file + if not fname.endswith('.py'): + warn('File must end with .py to be run using execfile: <%s>' % fname) - def syspath_cleanup(): - """Internal cleanup routine for sys.path.""" - if add_dname: - try: - sys.path.remove(dname) - except ValueError: - # For some reason the user has already removed it, ignore. - pass - - fname = os.path.expanduser(fname) + # Make sure we can open the file + try: + with open(fname) as thefile: + pass + except: + warn('Could not open file <%s> for safe execution.' % fname) + return # Find things also in current directory. This is needed to mimic the # behavior of running a script from the system command line, where # Python inserts the script's directory into sys.path - dname = os.path.dirname(os.path.abspath(fname)) - add_dname = False - if dname not in sys.path: - sys.path.insert(0,dname) - add_dname = True - - try: - xfile = open(fname) - except: - print >> Term.cerr, \ - 'Could not open file <%s> for safe execution.' % fname - syspath_cleanup() - return None + dname = os.path.dirname(fname) - kw.setdefault('islog',0) - kw.setdefault('quiet',1) - kw.setdefault('exit_ignore',0) - - first = xfile.readline() - loghead = str(self.loghead_tpl).split('\n',1)[0].strip() - xfile.close() - # line by line execution - if first.startswith(loghead) or kw['islog']: - print 'Loading log file <%s> one line at a time...' % fname - if kw['quiet']: - stdout_save = sys.stdout - sys.stdout = StringIO.StringIO() - try: - globs,locs = where[0:2] - except: - try: - globs = locs = where[0] - except: - globs = locs = globals() - badblocks = [] - - # we also need to identify indented blocks of code when replaying - # logs and put them together before passing them to an exec - # statement. This takes a bit of regexp and look-ahead work in the - # file. It's easiest if we swallow the whole thing in memory - # first, and manually walk through the lines list moving the - # counter ourselves. - indent_re = re.compile('\s+\S') - xfile = open(fname) - filelines = xfile.readlines() - xfile.close() - nlines = len(filelines) - lnum = 0 - while lnum < nlines: - line = filelines[lnum] - lnum += 1 - # don't re-insert logger status info into cache - if line.startswith('#log#'): - continue - else: - # build a block of code (maybe a single line) for execution - block = line - try: - next = filelines[lnum] # lnum has already incremented - except: - next = None - while next and indent_re.match(next): - block += next - lnum += 1 - try: - next = filelines[lnum] - except: - next = None - # now execute the block of one or more lines - try: - exec block in globs,locs - except SystemExit: - pass - except: - badblocks.append(block.rstrip()) - if kw['quiet']: # restore stdout - sys.stdout.close() - sys.stdout = stdout_save - print 'Finished replaying log file <%s>' % fname - if badblocks: - print >> sys.stderr, ('\nThe following lines/blocks in file ' - '<%s> reported errors:' % fname) - - for badline in badblocks: - print >> sys.stderr, badline - else: # regular file execution + with prepended_to_syspath(dname): try: if sys.platform == 'win32' and sys.version_info < (2,5,1): # Work around a bug in Python for Windows. The bug was @@ -1997,7 +1931,7 @@ class InteractiveShell(Component, Magic): except SyntaxError: self.showsyntaxerror() warn('Failure executing file: <%s>' % fname) - except SystemExit,status: + except SystemExit, status: # Code that correctly sets the exit status flag to success (0) # shouldn't be bothered with a traceback. Note that a plain # sys.exit() does NOT set the message to 0 (it's empty) so that @@ -2005,13 +1939,8 @@ class InteractiveShell(Component, Magic): # SystemExit exception changed between Python 2.4 and 2.5, so # the checks must be done in a version-dependent way. show = False - - if sys.version_info[:2] > (2,5): - if status.message!=0 and not kw['exit_ignore']: - show = True - else: - if status.code and not kw['exit_ignore']: - show = True + if status.message!=0 and not kw['exit_ignore']: + show = True if show: self.showtraceback() warn('Failure executing file: <%s>' % fname) @@ -2019,46 +1948,82 @@ class InteractiveShell(Component, Magic): self.showtraceback() warn('Failure executing file: <%s>' % fname) - syspath_cleanup() + def safe_execfile_ipy(self, fname): + """Like safe_execfile, but for .ipy files with IPython syntax. + + Parameters + ---------- + fname : str + The name of the file to execute. The filename must have a + .ipy extension. + """ + fname = os.path.abspath(os.path.expanduser(fname)) + + # Make sure we have a .py file + if not fname.endswith('.ipy'): + warn('File must end with .py to be run using execfile: <%s>' % fname) + + # Make sure we can open the file + try: + with open(fname) as thefile: + pass + except: + warn('Could not open file <%s> for safe execution.' % fname) + return + + # Find things also in current directory. This is needed to mimic the + # behavior of running a script from the system command line, where + # Python inserts the script's directory into sys.path + dname = os.path.dirname(fname) + + with prepended_to_syspath(dname): + try: + with open(fname) as thefile: + script = thefile.read() + # self.runlines currently captures all exceptions + # raise in user code. It would be nice if there were + # versions of runlines, execfile that did raise, so + # we could catch the errors. + self.runlines(script, clean=True) + except: + self.showtraceback() + warn('Unknown failure executing file: <%s>' % fname) + + def _is_secondary_block_start(self, s): + if not s.endswith(':'): + return False + if (s.startswith('elif') or + s.startswith('else') or + s.startswith('except') or + s.startswith('finally')): + return True def cleanup_ipy_script(self, script): """Make a script safe for self.runlines() - Notes - ----- - This was copied over from the old ipapi and probably can be done - away with once we move to block based interpreter. - - - Removes empty lines Suffixes all indented blocks that end with - - unindented lines with empty lines + Currently, IPython is lines based, with blocks being detected by + empty lines. This is a problem for block based scripts that may + not have empty lines after blocks. This script adds those empty + lines to make scripts safe for running in the current line based + IPython. """ - res = [] lines = script.splitlines() - level = 0 + for l in lines: lstripped = l.lstrip() stripped = l.strip() if not stripped: continue - newlevel = len(l) - len(lstripped) - def is_secondary_block_start(s): - if not s.endswith(':'): - return False - if (s.startswith('elif') or - s.startswith('else') or - s.startswith('except') or - s.startswith('finally')): - return True - + newlevel = len(l) - len(lstripped) if level > 0 and newlevel == 0 and \ - not is_secondary_block_start(stripped): + not self._is_secondary_block_start(stripped): # add empty line res.append('') - res.append(l) level = newlevel + return '\n'.join(res) + '\n' def runlines(self, lines, clean=False): @@ -2091,7 +2056,8 @@ class InteractiveShell(Component, Magic): if line or more: # push to raw history, so hist line numbers stay in sync self.input_hist_raw.append("# " + line + "\n") - more = self.push_line(self.prefilter_manager.prefilter_lines(line,more)) + prefiltered = self.prefilter_manager.prefilter_lines(line,more) + more = self.push_line(prefiltered) # IPython's runsource returns None if there was an error # compiling the code. This allows us to stop processing right # away, so the user gets the error message at the right place. @@ -2304,7 +2270,7 @@ class InteractiveShell(Component, Magic): if line.strip(): if continue_prompt: self.input_hist_raw[-1] += '%s\n' % line - if self.has_readline: # and some config option is set? + if self.has_readline and self.readline_use: try: histlen = self.readline.get_current_history_length() if histlen > 1: @@ -2349,37 +2315,56 @@ class InteractiveShell(Component, Magic): # if batchrun and not self.interactive: # self.ask_exit() - # def load(self, mod): - # """ Load an extension. - # - # Some modules should (or must) be 'load()':ed, rather than just imported. - # - # Loading will do: - # - # - run init_ipython(ip) - # - run ipython_firstrun(ip) - # """ - # - # if mod in self.extensions: - # # just to make sure we don't init it twice - # # note that if you 'load' a module that has already been - # # imported, init_ipython gets run anyway - # - # return self.extensions[mod] - # __import__(mod) - # m = sys.modules[mod] - # if hasattr(m,'init_ipython'): - # m.init_ipython(self) - # - # if hasattr(m,'ipython_firstrun'): - # already_loaded = self.db.get('firstrun_done', set()) - # if mod not in already_loaded: - # m.ipython_firstrun(self) - # already_loaded.add(mod) - # self.db['firstrun_done'] = already_loaded - # - # self.extensions[mod] = m - # return m + #------------------------------------------------------------------------- + # IPython extensions + #------------------------------------------------------------------------- + + def load_extension(self, module_str): + """Load an IPython extension. + + An IPython extension is an importable Python module that has + a function with the signature:: + + def load_in_ipython(ipython): + # Do things with ipython + + This function is called after your extension is imported and the + currently active :class:`InteractiveShell` instance is passed as + the only argument. You can do anything you want with IPython at + that point, including defining new magic and aliases, adding new + components, etc. + + You can put your extension modules anywhere you want, as long as + they can be imported by Python's standard import mechanism. However, + to make it easy to write extensions, you can also put your extensions + in ``os.path.join(self.ipythondir, 'extensions')``. This directory + is added to ``sys.path`` automatically. + """ + from IPython.utils.syspathcontext import prepended_to_syspath + + if module_str in sys.modules: + return + + with prepended_to_syspath(self.ipython_extension_dir): + __import__(module_str) + mod = sys.modules[module_str] + self._call_load_in_ipython(mod) + + def reload_extension(self, module_str): + """Reload an IPython extension by doing reload.""" + from IPython.utils.syspathcontext import prepended_to_syspath + + with prepended_to_syspath(self.ipython_extension_dir): + if module_str in sys.modules: + mod = sys.modules[module_str] + reload(mod) + self._call_load_in_ipython(mod) + else: + self.load_extension(self, module_str) + + def _call_load_in_ipython(self, mod): + if hasattr(mod, 'load_in_ipython'): + mod.load_in_ipython(self) #------------------------------------------------------------------------- # Things related to the prefilter diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 9d33b97..678e2b0 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -1573,7 +1573,7 @@ Currently the magic system has the following functions:\n""" return if filename.lower().endswith('.ipy'): - self.runlines(open(filename).read(), clean=True) + self.safe_execfile_ipy(filename) return # Control the response to exit() calls made by the script being run @@ -1743,25 +1743,6 @@ Currently the magic system has the following functions:\n""" return stats - def magic_runlog(self, parameter_s =''): - """Run files as logs. - - Usage:\\ - %runlog file1 file2 ... - - Run the named files (treating them as log files) in sequence inside - the interpreter, and return to the prompt. This is much slower than - %run because each line is executed in a try/except block, but it - allows running files with syntax errors in them. - - Normally IPython will guess when a file is one of its own logfiles, so - you can typically use %run even for logs. This shorthand allows you to - force any file to be treated as a log file.""" - - for f in parameter_s.split(): - self.shell.safe_execfile(f,self.shell.user_ns, - self.shell.user_ns,islog=1) - @testdec.skip_doctest def magic_timeit(self, parameter_s =''): """Time execution of a Python statement or expression diff --git a/IPython/core/page.py b/IPython/core/page.py index 7de15af..f07c1b5 100644 --- a/IPython/core/page.py +++ b/IPython/core/page.py @@ -127,12 +127,14 @@ def page(strng,start=0,screen_lines=0,pager_cmd = None): # auto-determine screen size if screen_lines <= 0: - if TERM=='xterm': + if TERM=='xterm' or TERM=='xterm-color': use_curses = USE_CURSES else: # curses causes problems on many terminals other than xterm. use_curses = False if use_curses: + import termios + import curses # There is a bug in curses, where *sometimes* it fails to properly # initialize, and then after the endwin() call is made, the # terminal is left in an unusable state. Rather than trying to diff --git a/IPython/core/prefilter.py b/IPython/core/prefilter.py index fd994a1..91983eb 100644 --- a/IPython/core/prefilter.py +++ b/IPython/core/prefilter.py @@ -32,6 +32,7 @@ from IPython.core.alias import AliasManager from IPython.core.autocall import IPyAutocall from IPython.core.component import Component from IPython.core.splitinput import split_user_input +from IPython.core.page import page from IPython.utils.traitlets import List, Int, Any, Str, CBool from IPython.utils.genutils import make_quoted_expr @@ -183,11 +184,9 @@ class PrefilterManager(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] def init_checkers(self): self._checkers = [] @@ -319,11 +318,9 @@ class PrefilterChecker(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] @auto_attr def prefilter_manager(self): @@ -523,11 +520,9 @@ class PrefilterHandler(Component): @auto_attr def shell(self): - shell = Component.get_instances( + return Component.get_instances( root=self.root, - klass='IPython.core.iplib.InteractiveShell' - )[0] - return shell + klass='IPython.core.iplib.InteractiveShell')[0] @auto_attr def prefilter_manager(self): diff --git a/IPython/utils/syspathcontext.py b/IPython/utils/syspathcontext.py new file mode 100644 index 0000000..65f202e --- /dev/null +++ b/IPython/utils/syspathcontext.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Context managers for adding things to sys.path temporarily. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +import sys + +class appended_to_syspath(object): + """A context for appending a directory to sys.path for a second.""" + + def __init__(self, dir): + self.dir = dir + + def __enter__(self): + if self.dir not in sys.path: + sys.path.append(self.dir) + self.added = True + else: + self.added = False + + def __exit__(self, type, value, traceback): + if self.added: + try: + sys.path.remove(self.dir) + except ValueError: + pass + # Returning False causes any exceptions to be re-raised. + return False + +class prepended_to_syspath(object): + """A context for prepending a directory to sys.path for a second.""" + + def __init__(self, dir): + self.dir = dir + + def __enter__(self): + if self.dir not in sys.path: + sys.path.insert(0,self.dir) + self.added = True + else: + self.added = False + + def __exit__(self, type, value, traceback): + if self.added: + try: + sys.path.remove(self.dir) + except ValueError: + pass + # Returning False causes any exceptions to be re-raised. + return False