diff --git a/IPython/__init__.py b/IPython/__init__.py index c8ad53e..90d7565 100755 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -16,10 +16,10 @@ IPython is a set of tools for interactive and exploratory computing in Python. #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- +from __future__ import absolute_import import os import sys -from IPython.core import release #----------------------------------------------------------------------------- # Setup everything @@ -38,12 +38,16 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) #----------------------------------------------------------------------------- # In some cases, these are causing circular imports. -from IPython.core.iplib import InteractiveShell -from IPython.core.embed import embed -from IPython.core.error import TryNext -from IPython.testing import test +from .config.loader import Config +from .core import release +from .core.application import Application +from .core.ipapp import IPythonApp +from .core.embed import embed +from .core.error import TryNext +from .core.iplib import InteractiveShell +from .testing import test -from IPython.lib import ( +from .lib import ( enable_wx, disable_wx, enable_gtk, disable_gtk, enable_qt4, disable_qt4, diff --git a/IPython/core/application.py b/IPython/core/application.py index ec347a5..d916a47 100755 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -84,27 +84,62 @@ app_cl_args = ( ) class Application(object): - """Load a config, construct components and set them running.""" + """Load a config, construct components and set them running. + + The configuration of an application can be done via four different Config + objects, which are loaded and ultimately merged into a single one used from + that point on by the app. These are: + + 1. default_config: internal defaults, implemented in code. + 2. file_config: read from the filesystem. + 3. command_line_config: read from the system's command line flags. + 4. constructor_config: passed parametrically to the constructor. + + During initialization, 3 is actually read before 2, since at the + command-line one may override the location of the file to be read. But the + above is the order in which the merge is made. + + There is a final config object can be created and passed to the + constructor: override_config. If it exists, this completely overrides the + configs 2-4 above (the default is still used to ensure that all needed + fields at least are created). This makes it easier to create + parametrically (e.g. in testing or sphinx plugins) objects with a known + configuration, that are unaffected by whatever arguments may be present in + sys.argv or files in the user's various directories. + """ name = u'ipython' description = 'IPython: an enhanced interactive Python shell.' #: usage message printed by argparse. If None, auto-generate usage = None config_file_name = u'ipython_config.py' - # Track the default and actual separately because some messages are - # only printed if we aren't using the default. + #: Track the default and actual separately because some messages are + #: only printed if we aren't using the default. default_config_file_name = config_file_name default_log_level = logging.WARN - # Set by --profile option + #: Set by --profile option profile_name = None #: User's ipython directory, typically ~/.ipython/ ipython_dir = None + #: internal defaults, implemented in code. + default_config = None + #: read from the filesystem + file_config = None + #: read from the system's command line flags + command_line_config = None + #: passed parametrically to the constructor. + constructor_config = None + #: final override, if given supercedes file/command/constructor configs + override_config = None #: A reference to the argv to be used (typically ends up being sys.argv[1:]) argv = None #: Default command line arguments. Subclasses should create a new tuple #: that *includes* these. cl_arguments = app_cl_args + #: extra arguments computed by the command-line loader + extra_args = None + # Private attributes _exiting = False _initialized = False @@ -112,8 +147,10 @@ class Application(object): # Class choices for things that will be instantiated at runtime. _CrashHandler = crashhandler.CrashHandler - def __init__(self, argv=None): + def __init__(self, argv=None, constructor_config=None, override_config=None): self.argv = sys.argv[1:] if argv is None else argv + self.constructor_config = constructor_config + self.override_config = override_config self.init_logger() def init_logger(self): @@ -134,7 +171,14 @@ class Application(object): log_level = property(_get_log_level, _set_log_level) def initialize(self): - """Start the application.""" + """Initialize the application. + + Loads all configuration information and sets all application state, but + does not start any relevant processing (typically some kind of event + loop). + + Once this method has been called, the application is flagged as + initialized and the method becomes a no-op.""" if self._initialized: return @@ -144,31 +188,50 @@ class Application(object): # handler is in place, we can let any subsequent exception propagate, # as our handler will log it with much better detail than the default. self.attempt(self.create_crash_handler) + + # Configuration phase + # Default config (internally hardwired in application code) self.create_default_config() self.log_default_config() self.set_default_config_log_level() - self.pre_load_command_line_config() - self.load_command_line_config() - self.set_command_line_config_log_level() - self.post_load_command_line_config() - self.log_command_line_config() + + if self.override_config is None: + # Command-line config + self.pre_load_command_line_config() + self.load_command_line_config() + self.set_command_line_config_log_level() + self.post_load_command_line_config() + self.log_command_line_config() + + # Find resources needed for filesystem access, using information from + # the above two self.find_ipython_dir() self.find_resources() self.find_config_file_name() self.find_config_file_paths() - self.pre_load_file_config() - self.load_file_config() - self.set_file_config_log_level() - self.post_load_file_config() - self.log_file_config() + + if self.override_config is None: + # File-based config + self.pre_load_file_config() + self.load_file_config() + self.set_file_config_log_level() + self.post_load_file_config() + self.log_file_config() + + # Merge all config objects into a single one the app can then use self.merge_configs() self.log_master_config() + + # Construction phase self.pre_construct() self.construct() self.post_construct() + + # Done, flag as such and self._initialized = True def start(self): + """Start the application.""" self.initialize() self.start_app() @@ -356,9 +419,19 @@ class Application(object): """Merge the default, command line and file config objects.""" config = Config() config._merge(self.default_config) - config._merge(self.file_config) - config._merge(self.command_line_config) + if self.override_config is None: + config._merge(self.file_config) + config._merge(self.command_line_config) + if self.constructor_config is not None: + config._merge(self.constructor_config) + else: + config._merge(self.override_config) + # XXX fperez - propose to Brian we rename master_config to simply + # config, I think this is going to be heavily used in examples and + # application code and the name is shorter/easier to find/remember. + # For now, just alias it... self.master_config = config + self.config = config def log_master_config(self): self.log.debug("Master config created:") diff --git a/IPython/core/ipapp.py b/IPython/core/ipapp.py index 37a92bd..95dd2c1 100755 --- a/IPython/core/ipapp.py +++ b/IPython/core/ipapp.py @@ -361,20 +361,33 @@ class IPythonApp(Application): # Private and configuration attributes _CrashHandler = crashhandler.IPythonCrashHandler - def __init__(self, argv=None, **shell_params): + def __init__(self, argv=None, + constructor_config=None, override_config=None, + **shell_params): """Create a new IPythonApp. + See the parent class for details on how configuration is handled. + Parameters ---------- argv : optional, list If given, used as the command-line argv environment to read arguments from. + constructor_config : optional, Config + If given, additional config that is merged last, after internal + defaults, command-line and file-based configs. + + override_config : optional, Config + If given, config that overrides all others unconditionally (except + for internal defaults, which ensure that all parameters exist). + shell_params : optional, dict All other keywords are passed to the :class:`iplib.InteractiveShell` constructor. """ - super(IPythonApp, self).__init__(argv) + super(IPythonApp, self).__init__(argv, constructor_config, + override_config) self.shell_params = shell_params def create_default_config(self): @@ -449,8 +462,7 @@ class IPythonApp(Application): # unless the -i flag (Global.force_interact) is true. code_to_run = config.Global.get('code_to_run','') file_to_run = False - if len(self.extra_args)>=1: - if self.extra_args[0]: + if self.extra_args and self.extra_args[0]: file_to_run = True if file_to_run or code_to_run: if not config.Global.force_interact: diff --git a/docs/sphinxext/ipython_directive.py b/docs/sphinxext/ipython_directive.py new file mode 100644 index 0000000..a7dc1c2 --- /dev/null +++ b/docs/sphinxext/ipython_directive.py @@ -0,0 +1,641 @@ +# -*- coding: utf-8 -*- +"""Sphinx directive to support embedded IPython code. + +This directive allows pasting of entire interactive IPython sessions, prompts +and all, and their code will actually get re-executed at doc build time, with +all prompts renumbered sequentially. + +To enable this directive, simply list it in your Sphinx ``conf.py`` file +(making sure the directory where you placed it is visible to sphinx, as is +needed for all Sphinx directives). + +By default this directive assumes that your prompts are unchanged IPython ones, +but this can be customized. For example, the following code in your Sphinx +config file will configure this directive for the following input/output +prompts ``Yade [1]:`` and ``-> [1]:``:: + + import ipython_directive as id + id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*') + id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*') + id.fmtin ='Yade [%d]:' + id.fmtout=' -> [%d]:' + + from IPython import Config + id.CONFIG = Config( + prompt_in1="Yade [\#]:", + prompt_in2=" .\D..", + prompt_out=" -> [\#]:" + ) + id.reconfig_shell() + + import ipython_console_highlighting as ich + ich.IPythonConsoleLexer.input_prompt= + re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)") + ich.IPythonConsoleLexer.output_prompt= + re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)") + ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:") + + +ToDo +---- + +- Turn the ad-hoc test() function into a real test suite. +- Break up ipython-specific functionality from matplotlib stuff into better + separated code. +- Make sure %bookmarks used internally are removed on exit. + + +Authors +------- + +- John D Hunter: orignal author. +- Fernando Perez: refactoring, documentation, cleanups, port to 0.11. +- VáclavŠmilauer : Prompt generalizations. +""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib +import cStringIO +import imp +import os +import re +import shutil +import sys +import warnings + +# To keep compatibility with various python versions +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + +# Third-party +import matplotlib +import sphinx +from docutils.parsers.rst import directives + +matplotlib.use('Agg') + +# Our own +from IPython import Config, IPythonApp +from IPython.utils.genutils import Term, Tee + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +sphinx_version = sphinx.__version__.split(".") +# The split is necessary for sphinx beta versions where the string is +# '6b1' +sphinx_version = tuple([int(re.split('[a-z]', x)[0]) + for x in sphinx_version[:2]]) + +COMMENT, INPUT, OUTPUT = range(3) +CONFIG = Config() +rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*') +rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*') +fmtin = 'In [%d]:' +fmtout = 'Out[%d]:' + +#----------------------------------------------------------------------------- +# Functions and class declarations +#----------------------------------------------------------------------------- +def block_parser(part): + """ + part is a string of ipython text, comprised of at most one + input, one ouput, comments, and blank lines. The block parser + parses the text into a list of:: + + blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] + + where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and + data is, depending on the type of token:: + + COMMENT : the comment string + + INPUT: the (DECORATOR, INPUT_LINE, REST) where + DECORATOR: the input decorator (or None) + INPUT_LINE: the input as string (possibly multi-line) + REST : any stdout generated by the input line (not OUTPUT) + + + OUTPUT: the output string, possibly multi-line + """ + + block = [] + lines = part.split('\n') + N = len(lines) + i = 0 + decorator = None + while 1: + + if i==N: + # nothing left to parse -- the last line + break + + line = lines[i] + i += 1 + line_stripped = line.strip() + if line_stripped.startswith('#'): + block.append((COMMENT, line)) + continue + + if line_stripped.startswith('@'): + # we're assuming at most one decorator -- may need to + # rethink + decorator = line_stripped + continue + + # does this look like an input line? + matchin = rgxin.match(line) + if matchin: + lineno, inputline = int(matchin.group(1)), matchin.group(2) + + # the ....: continuation string + continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) + Nc = len(continuation) + # input lines can continue on for more than one line, if + # we have a '\' line continuation char or a function call + # echo line 'print'. The input line can only be + # terminated by the end of the block or an output line, so + # we parse out the rest of the input line if it is + # multiline as well as any echo text + + rest = [] + while i2: + if debug: + print '\n'.join(lines) + else: + #print 'INSERTING %d lines'%len(lines) + state_machine.insert_input( + lines, state_machine.input_lines.source(0)) + + return [] + +ipython_directive.DEBUG = False +ipython_directive.DEBUG = True # dbg + +# Enable as a proper Sphinx directive +def setup(app): + setup.app = app + options = {'suppress': directives.flag, + 'doctest': directives.flag, + 'verbatim': directives.flag, + } + + app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options) + + +# Simple smoke test, needs to be converted to a proper automatic test. +def test(): + + examples = [ + r""" +In [9]: pwd +Out[9]: '/home/jdhunter/py4science/book' + +In [10]: cd bookdata/ +/home/jdhunter/py4science/book/bookdata + +In [2]: from pylab import * + +In [2]: ion() + +In [3]: im = imread('stinkbug.png') + +@savefig mystinkbug.png width=4in +In [4]: imshow(im) +Out[4]: + +""", + r""" + +In [1]: x = 'hello world' + +# string methods can be +# used to alter the string +@doctest +In [2]: x.upper() +Out[2]: 'HELLO WORLD' + +@verbatim +In [3]: x.st +x.startswith x.strip +""", + r""" + +In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ + .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' + +In [131]: print url.split('&') +['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] + +In [60]: import urllib + +""", + r"""\ + +In [133]: import numpy.random + +@suppress +In [134]: numpy.random.seed(2358) + +@doctest +In [135]: np.random.rand(10,2) +Out[135]: +array([[ 0.64524308, 0.59943846], + [ 0.47102322, 0.8715456 ], + [ 0.29370834, 0.74776844], + [ 0.99539577, 0.1313423 ], + [ 0.16250302, 0.21103583], + [ 0.81626524, 0.1312433 ], + [ 0.67338089, 0.72302393], + [ 0.7566368 , 0.07033696], + [ 0.22591016, 0.77731835], + [ 0.0072729 , 0.34273127]]) + +""", + + r""" +In [106]: print x +jdh + +In [109]: for i in range(10): + .....: print i + .....: + .....: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +""", + + r""" + +In [144]: from pylab import * + +In [145]: ion() + +# use a semicolon to suppress the output +@savefig test_hist.png width=4in +In [151]: hist(np.random.randn(10000), 100); + + +@savefig test_plot.png width=4in +In [151]: plot(np.random.randn(10000), 'o'); + """, + + r""" +# use a semicolon to suppress the output +In [151]: plt.clf() + +@savefig plot_simple.png width=4in +In [151]: plot([1,2,3]) + +@savefig hist_simple.png width=4in +In [151]: hist(np.random.randn(10000), 100); + +""", + r""" +# update the current fig +In [151]: ylabel('number') + +In [152]: title('normal distribution') + + +@savefig hist_with_text.png +In [153]: grid(True) + + """, + ] + + #ipython_directive.DEBUG = True # dbg + #options = dict(suppress=True) # dbg + options = dict() + for example in examples: + content = example.split('\n') + ipython_directive('debug', arguments=None, options=options, + content=content, lineno=0, + content_offset=None, block_text=None, + state=None, state_machine=None, + ) + +# Run test suite as a script +if __name__=='__main__': + if not os.path.isdir('_static'): + os.mkdir('_static') + test() + print 'All OK? Check figures in _static/'