Skip to content
Browse files

Merge branch 'master' of git://github.com/ipython/ipython into fronte…

…nd-logging
  • Loading branch information...
2 parents 85382a1 + 5cd1ae1 commit 5809ebf294b4615e555d09e29b4964212da29232 @omazapa omazapa committed May 26, 2011
Showing with 2,502 additions and 870 deletions.
  1. +230 −0 IPython/config/application.py
  2. +88 −2 IPython/config/configurable.py
  3. +120 −8 IPython/config/loader.py
  4. +105 −0 IPython/config/tests/test_application.py
  5. +48 −8 IPython/config/tests/test_configurable.py
  6. +22 −6 IPython/config/tests/test_loader.py
  7. +8 −0 IPython/core/display.py
  8. +53 −17 IPython/core/formatters.py
  9. +2 −2 IPython/core/history.py
  10. +82 −40 IPython/core/interactiveshell.py
  11. +1 −2 IPython/core/logger.py
  12. +170 −139 IPython/core/magic.py
  13. +80 −231 IPython/core/oinspect.py
  14. +19 −0 IPython/core/tests/test_logger.py
  15. +42 −0 IPython/core/tests/test_oinspect.py
  16. +1 −1 IPython/core/usage.py
  17. +4 −4 IPython/extensions/parallelmagic.py
  18. +2 −2 IPython/extensions/{sympy_printing.py → sympyprinting.py}
  19. +5 −5 IPython/frontend/qt/console/ipython_widget.py
  20. +2 −2 IPython/frontend/terminal/interactiveshell.py
  21. +49 −1 IPython/lib/latextools.py
  22. +28 −15 IPython/lib/pretty.py
  23. +2 −1 IPython/lib/pylabtools.py
  24. +3 −3 IPython/parallel/__init__.py
  25. +30 −1 IPython/parallel/apps/clusterdir.py
  26. +43 −19 IPython/parallel/apps/ipclusterapp.py
  27. +65 −1 IPython/parallel/client/client.py
  28. +4 −4 IPython/parallel/client/remotefunction.py
  29. +23 −15 IPython/parallel/client/view.py
  30. +1 −1 IPython/parallel/controller/dictdb.py
  31. +132 −43 IPython/parallel/controller/hub.py
  32. +17 −12 IPython/parallel/controller/mongodb.py
  33. +76 −32 IPython/parallel/controller/scheduler.py
  34. +25 −11 IPython/parallel/controller/sqlitedb.py
  35. +32 −23 IPython/parallel/streamsession.py
  36. +1 −1 IPython/parallel/tests/__init__.py
  37. +30 −0 IPython/parallel/tests/test_client.py
  38. +7 −19 IPython/parallel/tests/test_db.py
  39. +120 −0 IPython/parallel/tests/test_lbview.py
  40. +37 −0 IPython/parallel/tests/test_mongodb.py
  41. +1 −13 IPython/parallel/tests/test_view.py
  42. +0 −7 IPython/testing/decorators.py
  43. +4 −1 IPython/testing/iptest.py
  44. +1 −3 IPython/testing/plugin/test_refs.py
  45. +15 −0 IPython/testing/skipdoctest.py
  46. +4 −3 IPython/testing/tests/test_decorators.py
  47. +2 −0 IPython/utils/path.py
  48. +11 −1 IPython/utils/tests/test_path.py
  49. +75 −2 IPython/utils/tests/test_traitlets.py
  50. +280 −22 IPython/utils/traitlets.py
  51. +8 −3 IPython/zmq/ipkernel.py
  52. +1 −1 IPython/zmq/session.py
  53. +22 −140 IPython/zmq/zmqshell.py
  54. +7 −3 docs/do_sphinx.py
  55. +96 −0 docs/examples/core/appconfig.py
  56. +27 −0 docs/examples/core/display.py
  57. +1 −0 docs/source/parallel/index.txt
  58. +114 −0 docs/source/parallel/parallel_db.txt
  59. +24 −0 docs/source/parallel/parallel_task.txt
View
230 IPython/config/application.py
@@ -0,0 +1,230 @@
+# encoding: utf-8
+"""
+A base class for a configurable application.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 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
+#-----------------------------------------------------------------------------
+
+from copy import deepcopy
+import logging
+import sys
+
+from IPython.config.configurable import SingletonConfigurable
+from IPython.config.loader import (
+ KeyValueConfigLoader, PyFileConfigLoader, Config
+)
+
+from IPython.utils.traitlets import (
+ Unicode, List, Int, Enum, Dict
+)
+from IPython.utils.text import indent
+
+#-----------------------------------------------------------------------------
+# Descriptions for
+#-----------------------------------------------------------------------------
+
+flag_description = """
+Flags are command-line arguments passed as '--<flag>'.
+These take no parameters, unlike regular key-value arguments.
+They are typically used for setting boolean flags, or enabling
+modes that involve setting multiple options together.
+""".strip() # trim newlines of front and back
+
+alias_description = """
+These are commonly set parameters, given abbreviated aliases for convenience.
+They are set in the same `name=value` way as class parameters, where
+<name> is replaced by the real parameter for which it is an alias.
+""".strip() # trim newlines of front and back
+
+keyvalue_description = """
+Parameters are set from command-line arguments of the form:
+`Class.trait=value`. Parameters will *never* be prefixed with '-'.
+This line is evaluated in Python, so simple expressions are allowed, e.g.
+ `C.a='range(3)'` For setting C.a=[0,1,2]
+""".strip() # trim newlines of front and back
+
+#-----------------------------------------------------------------------------
+# Application class
+#-----------------------------------------------------------------------------
+
+
+class Application(SingletonConfigurable):
+ """A singleton application with full configuration support."""
+
+ # The name of the application, will usually match the name of the command
+ # line application
+ app_name = Unicode(u'application')
+
+ # The description of the application that is printed at the beginning
+ # of the help.
+ description = Unicode(u'This is an application.')
+ # default section descriptions
+ flag_description = Unicode(flag_description)
+ alias_description = Unicode(alias_description)
+ keyvalue_description = Unicode(keyvalue_description)
+
+
+ # A sequence of Configurable subclasses whose config=True attributes will
+ # be exposed at the command line.
+ classes = List([])
+
+ # The version string of this application.
+ version = Unicode(u'0.0')
+
+ # The log level for the application
+ log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
+ config=True,
+ help="Set the log level (0,10,20,30,40,50).")
+
+ # the alias map for configurables
+ aliases = Dict(dict(log_level='Application.log_level'))
+
+ # flags for loading Configurables or store_const style flags
+ # flags are loaded from this dict by '--key' flags
+ # this must be a dict of two-tuples, the first element being the Config/dict
+ # and the second being the help string for the flag
+ flags = Dict()
+
+
+ def __init__(self, **kwargs):
+ SingletonConfigurable.__init__(self, **kwargs)
+ # Add my class to self.classes so my attributes appear in command line
+ # options.
+ self.classes.insert(0, self.__class__)
+
+ # ensure self.flags dict is valid
+ for key,value in self.flags.iteritems():
+ assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
+ assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
+ assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
+ self.init_logging()
+
+ def init_logging(self):
+ """Start logging for this application.
+
+ The default is to log to stdout using a StreaHandler. The log level
+ starts at loggin.WARN, but this can be adjusted by setting the
+ ``log_level`` attribute.
+ """
+ self.log = logging.getLogger(self.__class__.__name__)
+ self.log.setLevel(self.log_level)
+ 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 _log_level_changed(self, name, old, new):
+ """Adjust the log level when log_level is set."""
+ self.log.setLevel(new)
+
+ def print_alias_help(self):
+ """print the alias part of the help"""
+ if not self.aliases:
+ return
+
+ print "Aliases"
+ print "-------"
+ print self.alias_description
+ print
+
+ classdict = {}
+ for c in self.classes:
+ classdict[c.__name__] = c
+
+ for alias, longname in self.aliases.iteritems():
+ classname, traitname = longname.split('.',1)
+ cls = classdict[classname]
+
+ trait = cls.class_traits(config=True)[traitname]
+ help = trait.get_metadata('help')
+ print alias, "(%s)"%longname, ':', trait.__class__.__name__
+ if help:
+ print indent(help)
+ print
+
+ def print_flag_help(self):
+ """print the flag part of the help"""
+ if not self.flags:
+ return
+
+ print "Flags"
+ print "-----"
+ print self.flag_description
+ print
+
+ for m, (cfg,help) in self.flags.iteritems():
+ print '--'+m
+ print indent(help)
+ print
+
+ def print_help(self):
+ """Print the help for each Configurable class in self.classes."""
+ self.print_flag_help()
+ self.print_alias_help()
+ if self.classes:
+ print "Class parameters"
+ print "----------------"
+ print self.keyvalue_description
+ print
+
+ for cls in self.classes:
+ cls.class_print_help()
+ print
+
+ def print_description(self):
+ """Print the application description."""
+ print self.description
+ print
+
+ def print_version(self):
+ """Print the version string."""
+ print self.version
+
+ def update_config(self, config):
+ """Fire the traits events when the config is updated."""
+ # Save a copy of the current config.
+ newconfig = deepcopy(self.config)
+ # Merge the new config into the current one.
+ newconfig._merge(config)
+ # Save the combined config as self.config, which triggers the traits
+ # events.
+ self.config = config
+
+ def parse_command_line(self, argv=None):
+ """Parse the command line arguments."""
+ argv = sys.argv[1:] if argv is None else argv
+
+ if '-h' in argv or '--help' in argv:
+ self.print_description()
+ self.print_help()
+ sys.exit(1)
+
+ if '--version' in argv:
+ self.print_version()
+ sys.exit(1)
+
+ loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
+ flags=self.flags)
+ config = loader.load_config()
+ self.update_config(config)
+
+ def load_config_file(self, filename, path=None):
+ """Load a .py based config file by filename and path."""
+ # TODO: this raises IOError if filename does not exist.
+ loader = PyFileConfigLoader(filename, path=path)
+ config = loader.load_config()
+ self.update_config(config)
+
View
90 IPython/config/configurable.py
@@ -22,11 +22,10 @@
from copy import deepcopy
import datetime
-from weakref import WeakValueDictionary
-from IPython.utils.importstring import import_item
from loader import Config
from IPython.utils.traitlets import HasTraits, Instance
+from IPython.utils.text import indent
#-----------------------------------------------------------------------------
@@ -38,6 +37,9 @@ class ConfigurableError(Exception):
pass
+class MultipleInstanceError(ConfigurableError):
+ pass
+
#-----------------------------------------------------------------------------
# Configurable implementation
#-----------------------------------------------------------------------------
@@ -137,3 +139,87 @@ def _config_changed(self, name, old, new):
# shared by all instances, effectively making it a class attribute.
setattr(self, k, deepcopy(config_value))
+ @classmethod
+ def class_get_help(cls):
+ """Get the help string for this class in ReST format."""
+ cls_traits = cls.class_traits(config=True)
+ final_help = []
+ final_help.append(u'%s options' % cls.__name__)
+ final_help.append(len(final_help[0])*u'-')
+ for k, v in cls_traits.items():
+ help = v.get_metadata('help')
+ header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
+ final_help.append(header)
+ if help is not None:
+ final_help.append(indent(help))
+ return '\n'.join(final_help)
+
+ @classmethod
+ def class_print_help(cls):
+ print cls.class_get_help()
+
+
+class SingletonConfigurable(Configurable):
+ """A configurable that only allows one instance.
+
+ This class is for classes that should only have one instance of itself
+ or *any* subclass. To create and retrieve such a class use the
+ :meth:`SingletonConfigurable.instance` method.
+ """
+
+ _instance = None
+
+ @classmethod
+ def instance(cls, *args, **kwargs):
+ """Returns a global instance of this class.
+
+ This method create a new instance if none have previously been created
+ and returns a previously created instance is one already exists.
+
+ The arguments and keyword arguments passed to this method are passed
+ on to the :meth:`__init__` method of the class upon instantiation.
+
+ Examples
+ --------
+
+ Create a singleton class using instance, and retrieve it::
+
+ >>> from IPython.config.configurable import SingletonConfigurable
+ >>> class Foo(SingletonConfigurable): pass
+ >>> foo = Foo.instance()
+ >>> foo == Foo.instance()
+ True
+
+ Create a subclass that is retrived using the base class instance::
+
+ >>> class Bar(SingletonConfigurable): pass
+ >>> class Bam(Bar): pass
+ >>> bam = Bam.instance()
+ >>> bam == Bar.instance()
+ True
+ """
+ # Create and save the instance
+ if cls._instance is None:
+ inst = cls(*args, **kwargs)
+ # Now make sure that the instance will also be returned by
+ # the subclasses instance attribute.
+ for subclass in cls.mro():
+ if issubclass(cls, subclass) and \
+ issubclass(subclass, SingletonConfigurable) and \
+ subclass != SingletonConfigurable:
+ subclass._instance = inst
+ else:
+ break
+ if isinstance(cls._instance, cls):
+ return cls._instance
+ else:
+ raise MultipleInstanceError(
+ 'Multiple incompatible subclass instances of '
+ '%s are being created.' % cls.__name__
+ )
+
+ @classmethod
+ def initialized(cls):
+ """Has an instance been created?"""
+ return hasattr(cls, "_instance") and cls._instance is not None
+
View
128 IPython/config/loader.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-# coding: utf-8
"""A simple configuration system.
Authors
@@ -20,7 +18,7 @@
#-----------------------------------------------------------------------------
import __builtin__
-import os
+import re
import sys
from IPython.external import argparse
@@ -306,8 +304,117 @@ class CommandLineConfigLoader(ConfigLoader):
here.
"""
+kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
+flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
+
+class KeyValueConfigLoader(CommandLineConfigLoader):
+ """A config loader that loads key value pairs from the command line.
+
+ This allows command line options to be gives in the following form::
+
+ ipython Global.profile="foo" InteractiveShell.autocall=False
+ """
+
+ def __init__(self, argv=None, aliases=None, flags=None):
+ """Create a key value pair config loader.
+
+ Parameters
+ ----------
+ argv : list
+ A list that has the form of sys.argv[1:] which has unicode
+ elements of the form u"key=value". If this is None (default),
+ then sys.argv[1:] will be used.
+ aliases : dict
+ A dict of aliases for configurable traits.
+ Keys are the short aliases, Values are the resolved trait.
+ Of the form: `{'alias' : 'Configurable.trait'}`
+ flags : dict
+ A dict of flags, keyed by str name. Vaues can be Config objects,
+ dicts, or "key=value" strings. If Config or dict, when the flag
+ is triggered, The flag is loaded as `self.config.update(m)`.
+
+ Returns
+ -------
+ config : Config
+ The resulting Config object.
+
+ Examples
+ --------
+
+ >>> from IPython.config.loader import KeyValueConfigLoader
+ >>> cl = KeyValueConfigLoader()
+ >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
+ {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
+ """
+ if argv is None:
+ argv = sys.argv[1:]
+ self.argv = argv
+ self.aliases = aliases or {}
+ self.flags = flags or {}
+
+ def load_config(self, argv=None, aliases=None, flags=None):
+ """Parse the configuration and generate the Config object.
+
+ Parameters
+ ----------
+ argv : list, optional
+ A list that has the form of sys.argv[1:] which has unicode
+ elements of the form u"key=value". If this is None (default),
+ then self.argv will be used.
+ aliases : dict
+ A dict of aliases for configurable traits.
+ Keys are the short aliases, Values are the resolved trait.
+ Of the form: `{'alias' : 'Configurable.trait'}`
+ flags : dict
+ A dict of flags, keyed by str name. Values can be Config objects
+ or dicts. When the flag is triggered, The config is loaded as
+ `self.config.update(cfg)`.
+ """
+ from IPython.config.configurable import Configurable
+
+ self.clear()
+ if argv is None:
+ argv = self.argv
+ if aliases is None:
+ aliases = self.aliases
+ if flags is None:
+ flags = self.flags
+
+ for item in argv:
+ if kv_pattern.match(item):
+ lhs,rhs = item.split('=',1)
+ # Substitute longnames for aliases.
+ if lhs in aliases:
+ lhs = aliases[lhs]
+ exec_str = 'self.config.' + lhs + '=' + rhs
+ try:
+ # Try to see if regular Python syntax will work. This
+ # won't handle strings as the quote marks are removed
+ # by the system shell.
+ exec exec_str in locals(), globals()
+ except (NameError, SyntaxError):
+ # This case happens if the rhs is a string but without
+ # the quote marks. We add the quote marks and see if
+ # it succeeds. If it still fails, we let it raise.
+ exec_str = 'self.config.' + lhs + '="' + rhs + '"'
+ exec exec_str in locals(), globals()
+ elif flag_pattern.match(item):
+ # trim leading '--'
+ m = item[2:]
+ cfg,_ = flags.get(m, (None,None))
+ if cfg is None:
+ raise ValueError("Unrecognized flag: %r"%item)
+ elif isinstance(cfg, (dict, Config)):
+ # update self.config with Config:
+ self.config.update(cfg)
+ else:
+ raise ValueError("Invalid flag: %r"%flag)
+ else:
+ raise ValueError("Invalid argument: %r"%item)
+ return self.config
class ArgParseConfigLoader(CommandLineConfigLoader):
+ """A loader that uses the argparse module to load from the command line."""
def __init__(self, argv=None, *parser_args, **parser_kw):
"""Create a config loader for use with argparse.
@@ -326,6 +433,11 @@ def __init__(self, argv=None, *parser_args, **parser_kw):
parser_kw : dict
A tuple of keyword arguments that will be passed to the
constructor of :class:`argparse.ArgumentParser`.
+
+ Returns
+ -------
+ config : Config
+ The resulting Config object.
"""
super(CommandLineConfigLoader, self).__init__()
if argv == None:
@@ -337,8 +449,8 @@ def __init__(self, argv=None, *parser_args, **parser_kw):
kwargs.update(parser_kw)
self.parser_kw = kwargs
- def load_config(self, args=None):
- """Parse command line arguments and return as a Struct.
+ def load_config(self, argv=None):
+ """Parse command line arguments and return as a Config object.
Parameters
----------
@@ -348,10 +460,10 @@ def load_config(self, args=None):
arguments from. If not given, the instance's self.argv attribute
(given at construction time) is used."""
self.clear()
- if args is None:
- args = self.argv
+ if argv is None:
+ argv = self.argv
self._create_parser()
- self._parse_args(args)
+ self._parse_args(argv)
self._convert_to_config()
return self.config
View
105 IPython/config/tests/test_application.py
@@ -0,0 +1,105 @@
+"""
+Tests for IPython.config.application.Application
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (C) 2008-2011 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
+#-----------------------------------------------------------------------------
+
+from unittest import TestCase
+
+from IPython.config.configurable import Configurable
+
+from IPython.config.application import (
+ Application
+)
+
+from IPython.utils.traitlets import (
+ Bool, Unicode, Int, Float, List, Dict
+)
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+class Foo(Configurable):
+
+ i = Int(0, config=True, help="The integer i.")
+ j = Int(1, config=True, help="The integer j.")
+ name = Unicode(u'Brian', config=True, help="First name.")
+
+
+class Bar(Configurable):
+
+ enabled = Bool(True, config=True, help="Enable bar.")
+
+
+class MyApp(Application):
+
+ app_name = Unicode(u'myapp')
+ running = Bool(False, config=True,
+ help="Is the app running?")
+ classes = List([Bar, Foo])
+ config_file = Unicode(u'', config=True,
+ help="Load this config file")
+
+ aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name',
+ enabled='Bar.enabled', log_level='MyApp.log_level'))
+
+ flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"),
+ disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False")))
+
+ def init_foo(self):
+ self.foo = Foo(config=self.config)
+
+ def init_bar(self):
+ self.bar = Bar(config=self.config)
+
+
+class TestApplication(TestCase):
+
+ def test_basic(self):
+ app = MyApp()
+ self.assertEquals(app.app_name, u'myapp')
+ self.assertEquals(app.running, False)
+ self.assertEquals(app.classes, [MyApp,Bar,Foo])
+ self.assertEquals(app.config_file, u'')
+
+ def test_config(self):
+ app = MyApp()
+ app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
+ config = app.config
+ self.assertEquals(config.Foo.i, 10)
+ self.assertEquals(config.Foo.j, 10)
+ self.assertEquals(config.Bar.enabled, False)
+ self.assertEquals(config.MyApp.log_level,0)
+
+ def test_config_propagation(self):
+ app = MyApp()
+ app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
+ app.init_foo()
+ app.init_bar()
+ self.assertEquals(app.foo.i, 10)
+ self.assertEquals(app.foo.j, 10)
+ self.assertEquals(app.bar.enabled, False)
+
+ def test_alias(self):
+ app = MyApp()
+ app.parse_command_line(["--disable"])
+ app.init_bar()
+ self.assertEquals(app.bar.enabled, False)
+ app.parse_command_line(["--enable"])
+ app.init_bar()
+ self.assertEquals(app.bar.enabled, True)
+
View
56 IPython/config/tests/test_configurable.py
@@ -22,10 +22,15 @@
from unittest import TestCase
-from IPython.config.configurable import Configurable, ConfigurableError
+from IPython.config.configurable import (
+ Configurable,
+ SingletonConfigurable
+)
+
from IPython.utils.traitlets import (
- TraitError, Int, Float, Str
+ Int, Float, Str
)
+
from IPython.config.loader import Config
@@ -35,22 +40,29 @@
class MyConfigurable(Configurable):
- a = Int(1, config=True)
- b = Float(1.0, config=True)
+ a = Int(1, config=True, help="The integer a.")
+ b = Float(1.0, config=True, help="The integer b.")
c = Str('no config')
+mc_help=u"""MyConfigurable options
+----------------------
+MyConfigurable.a : Int
+ The integer a.
+MyConfigurable.b : Float
+ The integer b."""
+
class Foo(Configurable):
- a = Int(0, config=True)
+ a = Int(0, config=True, help="The integer a.")
b = Str('nope', config=True)
class Bar(Foo):
- b = Str('gotit', config=False)
- c = Float(config=True)
+ b = Str('gotit', config=False, help="The string b.")
+ c = Float(config=True, help="The string c.")
-class TestConfigurableConfig(TestCase):
+class TestConfigurable(TestCase):
def test_default(self):
c1 = Configurable()
@@ -122,3 +134,31 @@ def test_override2(self):
self.assertEquals(c.a, 2)
self.assertEquals(c.b, 'and')
self.assertEquals(c.c, 20.0)
+
+ def test_help(self):
+ self.assertEquals(MyConfigurable.class_get_help(), mc_help)
+
+
+class TestSingletonConfigurable(TestCase):
+
+ def test_instance(self):
+ from IPython.config.configurable import SingletonConfigurable
+ class Foo(SingletonConfigurable): pass
+ self.assertEquals(Foo.initialized(), False)
+ foo = Foo.instance()
+ self.assertEquals(Foo.initialized(), True)
+ self.assertEquals(foo, Foo.instance())
+ self.assertEquals(SingletonConfigurable._instance, None)
+
+ def test_inheritance(self):
+ class Bar(SingletonConfigurable): pass
+ class Bam(Bar): pass
+ self.assertEquals(Bar.initialized(), False)
+ self.assertEquals(Bam.initialized(), False)
+ bam = Bam.instance()
+ bam == Bar.instance()
+ self.assertEquals(Bar.initialized(), True)
+ self.assertEquals(Bam.initialized(), True)
+ self.assertEquals(bam, Bam._instance)
+ self.assertEquals(bam, Bar._instance)
+ self.assertEquals(SingletonConfigurable._instance, None)
View
28 IPython/config/tests/test_loader.py
@@ -24,9 +24,12 @@
from tempfile import mkstemp
from unittest import TestCase
+from IPython.utils.traitlets import Int, Unicode
+from IPython.config.configurable import Configurable
from IPython.config.loader import (
Config,
- PyFileConfigLoader,
+ PyFileConfigLoader,
+ KeyValueConfigLoader,
ArgParseConfigLoader,
ConfigError
)
@@ -38,11 +41,11 @@
pyfile = """
c = get_config()
-c.a = 10
-c.b = 20
-c.Foo.Bar.value = 10
-c.Foo.Bam.value = range(10)
-c.D.C.value = 'hi there'
+c.a=10
+c.b=20
+c.Foo.Bar.value=10
+c.Foo.Bam.value=range(10)
+c.D.C.value='hi there'
"""
class TestPyFileCL(TestCase):
@@ -109,6 +112,19 @@ def test_argv(self):
self.assertEquals(config.Global.bam, 'wow')
+class TestKeyValueCL(TestCase):
+
+ def test_basic(self):
+ cl = KeyValueConfigLoader()
+ argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
+ config = cl.load_config(argv)
+ self.assertEquals(config.a, 10)
+ self.assertEquals(config.b, 20)
+ self.assertEquals(config.Foo.Bar.value, 10)
+ self.assertEquals(config.Foo.Bam.value, range(10))
+ self.assertEquals(config.D.C.value, 'hi there')
+
+
class TestConfig(TestCase):
def test_setget(self):
View
8 IPython/core/display.py
@@ -119,4 +119,12 @@ def display_json(*objs):
display(*objs, include=['text/plain','application/json'])
+def display_javascript(*objs):
+ """Display the Javascript representation of an object.
+ Parameters
+ ----------
+ objs : tuple of objects
+ The Python objects to display.
+ """
+ display(*objs, include=['text/plain','application/javascript'])
View
70 IPython/core/formatters.py
@@ -52,7 +52,8 @@ def _formatters_default(self):
SVGFormatter,
PNGFormatter,
LatexFormatter,
- JSONFormatter
+ JSONFormatter,
+ JavascriptFormatter
]
d = {}
for cls in formatter_classes:
@@ -220,22 +221,25 @@ def __call__(self, obj):
obj_id = id(obj)
try:
obj_class = getattr(obj, '__class__', None) or type(obj)
- if hasattr(obj_class, self.print_method):
- printer = getattr(obj_class, self.print_method)
- return printer(obj)
+ # First try to find registered singleton printers for the type.
try:
printer = self.singleton_printers[obj_id]
except (TypeError, KeyError):
pass
else:
return printer(obj)
+ # Next look for type_printers.
for cls in pretty._get_mro(obj_class):
if cls in self.type_printers:
return self.type_printers[cls](obj)
else:
printer = self._in_deferred_types(cls)
if printer is not None:
return printer(obj)
+ # Finally look for special method names.
+ if hasattr(obj_class, self.print_method):
+ printer = getattr(obj_class, self.print_method)
+ return printer(obj)
return None
except Exception:
pass
@@ -339,8 +343,8 @@ def dtype_pprinter(obj, p, cycle):
# something.
enabled = Bool(True, config=False)
- # Look for a __pretty__ methods to use for pretty printing.
- print_method = Str('__pretty__')
+ # Look for a _repr_pretty_ methods to use for pretty printing.
+ print_method = Str('_repr_pretty_')
# Whether to pretty-print or not.
pprint = Bool(True, config=True)
@@ -442,66 +446,97 @@ class HTMLFormatter(BaseFormatter):
"""An HTML formatter.
To define the callables that compute the HTML representation of your
- objects, define a :meth:`__html__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid HTML snippet that
+ could be injected into an existing DOM. It should *not* include the
+ ```<html>`` or ```<body>`` tags.
"""
format_type = Str('text/html')
- print_method = Str('__html__')
+ print_method = Str('_repr_html_')
class SVGFormatter(BaseFormatter):
"""An SVG formatter.
To define the callables that compute the SVG representation of your
- objects, define a :meth:`__svg__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be valid SVG enclosed in
+ ```<svg>``` tags, that could be injected into an existing DOM. It should
+ *not* include the ```<html>`` or ```<body>`` tags.
"""
format_type = Str('image/svg+xml')
- print_method = Str('__svg__')
+ print_method = Str('_repr_svg_')
class PNGFormatter(BaseFormatter):
"""A PNG formatter.
To define the callables that compute the PNG representation of your
- objects, define a :meth:`__png__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
- this. The raw data should be the base64 encoded raw png data.
+ this.
+
+ The return value of this formatter should be raw PNG data, *not*
+ base64 encoded.
"""
format_type = Str('image/png')
- print_method = Str('__png__')
+ print_method = Str('_repr_png_')
class LatexFormatter(BaseFormatter):
"""A LaTeX formatter.
To define the callables that compute the LaTeX representation of your
- objects, define a :meth:`__latex__` method or use the :meth:`for_type`
+ objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid LaTeX equation,
+ enclosed in either ```$``` or ```$$```.
"""
format_type = Str('text/latex')
- print_method = Str('__latex__')
+ print_method = Str('_repr_latex_')
class JSONFormatter(BaseFormatter):
"""A JSON string formatter.
To define the callables that compute the JSON string representation of
- your objects, define a :meth:`__json__` method or use the :meth:`for_type`
+ your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
or :meth:`for_type_by_name` methods to register functions that handle
this.
+
+ The return value of this formatter should be a valid JSON string.
"""
format_type = Str('application/json')
- print_method = Str('__json__')
+ print_method = Str('_repr_json_')
+
+
+class JavascriptFormatter(BaseFormatter):
+ """A Javascript formatter.
+
+ To define the callables that compute the Javascript representation of
+ your objects, define a :meth:`_repr_javascript_` method or use the
+ :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
+ that handle this.
+
+ The return value of this formatter should be valid Javascript code and
+ should *not* be enclosed in ```<script>``` tags.
+ """
+ format_type = Str('application/javascript')
+ print_method = Str('_repr_javascript_')
FormatterABC.register(BaseFormatter)
FormatterABC.register(PlainTextFormatter)
@@ -510,6 +545,7 @@ class JSONFormatter(BaseFormatter):
FormatterABC.register(PNGFormatter)
FormatterABC.register(LatexFormatter)
FormatterABC.register(JSONFormatter)
+FormatterABC.register(JavascriptFormatter)
def format_display_data(obj, include=None, exclude=None):
View
4 IPython/core/history.py
@@ -23,7 +23,7 @@
# Our own packages
from IPython.config.configurable import Configurable
-from IPython.testing import decorators as testdec
+from IPython.testing.skipdoctest import skip_doctest
from IPython.utils import io
from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
from IPython.utils.warn import warn
@@ -548,7 +548,7 @@ def _format_lineno(session, line):
return str(line)
return "%s#%s" % (session, line)
-@testdec.skip_doctest
+@skip_doctest
def magic_history(self, parameter_s = ''):
"""Print input history (_i<n> variables), with most recent last.
View
122 IPython/core/interactiveshell.py
@@ -31,7 +31,7 @@
import types
from contextlib import nested
-from IPython.config.configurable import Configurable
+from IPython.config.configurable import SingletonConfigurable
from IPython.core import debugger, oinspect
from IPython.core import history as ipcorehist
from IPython.core import page
@@ -132,9 +132,7 @@ def validate(self, obj, value):
value = value.replace('\\n','\n')
return super(SeparateStr, self).validate(obj, value)
-class MultipleInstanceError(Exception):
- pass
-
+
class ReadlineNoRecord(object):
"""Context manager to execute some code, then reload readline history
so that interactive input to the code doesn't appear when pressing up."""
@@ -181,25 +179,78 @@ def get_readline_tail(self, n=10):
return [ghi(x) for x in range(start, end)]
+_autocall_help = """
+Make IPython automatically call any callable object even if
+you didn't type explicit parentheses. For example, 'str 43' becomes 'str(43)'
+automatically. The value can be '0' to disable the feature, '1' for 'smart'
+autocall, where it is not applied if there are no more arguments on the line,
+and '2' for 'full' autocall, where all callable objects are automatically
+called (even if no arguments are present). The default is '1'.
+"""
+
#-----------------------------------------------------------------------------
# Main IPython class
#-----------------------------------------------------------------------------
-class InteractiveShell(Configurable, Magic):
+class InteractiveShell(SingletonConfigurable, Magic):
"""An enhanced, interactive shell for Python."""
_instance = None
- autocall = Enum((0,1,2), default_value=1, config=True)
+
+ autocall = Enum((0,1,2), default_value=1, config=True, help=
+ """
+ Make IPython automatically call any callable object even if you didn't
+ type explicit parentheses. For example, 'str 43' becomes 'str(43)'
+ automatically. The value can be '0' to disable the feature, '1' for
+ 'smart' autocall, where it is not applied if there are no more
+ arguments on the line, and '2' for 'full' autocall, where all callable
+ objects are automatically called (even if no arguments are present).
+ The default is '1'.
+ """
+ )
# TODO: remove all autoindent logic and put into frontends.
# We can't do this yet because even runlines uses the autoindent.
- autoindent = CBool(True, config=True)
- automagic = CBool(True, config=True)
- cache_size = Int(1000, config=True)
- color_info = CBool(True, config=True)
+ autoindent = CBool(True, config=True, help=
+ """
+ Autoindent IPython code entered interactively.
+ """
+ )
+ automagic = CBool(True, config=True, help=
+ """
+ Enable magic commands to be called without the leading %.
+ """
+ )
+ cache_size = Int(1000, config=True, help=
+ """
+ Set the size of the output cache. The default is 1000, you can
+ change it permanently in your config file. Setting it to 0 completely
+ disables the caching system, and the minimum value accepted is 20 (if
+ you provide a value less than 20, it is reset to 0 and a warning is
+ issued). This limit is defined because otherwise you'll spend more
+ time re-flushing a too small cache than working
+ """
+ )
+ color_info = CBool(True, config=True, help=
+ """
+ Use colors for displaying information about objects. Because this
+ information is passed through a pager (like 'less'), and some pagers
+ get confused with color codes, this capability can be turned off.
+ """
+ )
colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
default_value=get_default_colors(), config=True)
debug = CBool(False, config=True)
- deep_reload = CBool(False, config=True)
+ deep_reload = CBool(False, config=True, help=
+ """
+ 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().
+ """
+ )
display_formatter = Instance(DisplayFormatter)
displayhook_class = Type(DisplayHook)
display_pub_class = Type(DisplayPublisher)
@@ -217,12 +268,28 @@ def _exiter_default(self):
# interactive statements or whole blocks.
input_splitter = Instance('IPython.core.inputsplitter.IPythonInputSplitter',
(), {})
- logstart = CBool(False, config=True)
- logfile = Unicode('', config=True)
- logappend = Unicode('', config=True)
+ logstart = CBool(False, config=True, help=
+ """
+ Start logging to the default log file.
+ """
+ )
+ logfile = Unicode('', config=True, help=
+ """
+ The name of the logfile to use.
+ """
+ )
+ logappend = Unicode('', config=True, help=
+ """
+ Start logging to the given file in append mode.
+ """
+ )
object_info_string_level = Enum((0,1,2), default_value=0,
config=True)
- pdb = CBool(False, config=True)
+ pdb = CBool(False, config=True, help=
+ """
+ Automatically call the pdb debugger after every exception.
+ """
+ )
profile = Unicode('', config=True)
prompt_in1 = Str('In [\\#]: ', config=True)
@@ -356,31 +423,6 @@ def __init__(self, config=None, ipython_dir=None,
self.hooks.late_startup_hook()
atexit.register(self.atexit_operations)
- @classmethod
- def instance(cls, *args, **kwargs):
- """Returns a global InteractiveShell instance."""
- if cls._instance is None:
- inst = cls(*args, **kwargs)
- # Now make sure that the instance will also be returned by
- # the subclasses instance attribute.
- for subclass in cls.mro():
- if issubclass(cls, subclass) and \
- issubclass(subclass, InteractiveShell):
- subclass._instance = inst
- else:
- break
- if isinstance(cls._instance, cls):
- return cls._instance
- else:
- raise MultipleInstanceError(
- 'Multiple incompatible subclass instances of '
- 'InteractiveShell are being created.'
- )
-
- @classmethod
- def initialized(cls):
- return hasattr(cls, "_instance")
-
def get_ipython(self):
"""Return the currently running IPython instance."""
return self
View
3 IPython/core/logger.py
@@ -69,8 +69,6 @@ def logstart(self,logfname=None,loghead=None,logmode=None,
raise RuntimeError('Log file is already active: %s' %
self.logfname)
- self.log_active = True
-
# The parameters can override constructor defaults
if logfname is not None: self.logfname = logfname
if loghead is not None: self.loghead = loghead
@@ -124,6 +122,7 @@ def logstart(self,logfname=None,loghead=None,logmode=None,
self.logfile.write(self.loghead)
self.logfile.flush()
+ self.log_active = True
def switch_log(self,val):
"""Switch logging on/off. val should be ONLY a boolean."""
View
309 IPython/core/magic.py
@@ -51,7 +51,7 @@
from IPython.core.prefilter import ESC_MAGIC
from IPython.lib.pylabtools import mpl_runner
from IPython.external.Itpl import itpl, printpl
-from IPython.testing import decorators as testdec
+from IPython.testing.skipdoctest import skip_doctest
from IPython.utils.io import file_read, nlprint
from IPython.utils.path import get_py_filename
from IPython.utils.process import arg_split, abbrev_cwd
@@ -89,6 +89,9 @@ def needs_local_scope(func):
"""Decorator to mark magic functions which need to local scope to run."""
func.needs_local_scope = True
return func
+
+# Used for exception handling in magic_edit
+class MacroToEdit(ValueError): pass
#***************************************************************************
# Main class implementing Magic functionality
@@ -435,7 +438,7 @@ def magic_automagic(self, parameter_s = ''):
self.shell.automagic = not self.shell.automagic
print '\n' + Magic.auto_status[self.shell.automagic]
- @testdec.skip_doctest
+ @skip_doctest
def magic_autocall(self, parameter_s = ''):
"""Make functions callable without having to type parentheses.
@@ -564,7 +567,7 @@ def magic_pinfo2(self, parameter_s='', namespaces=None):
self.shell._inspect('pinfo', parameter_s, detail_level=1,
namespaces=namespaces)
- @testdec.skip_doctest
+ @skip_doctest
def magic_pdef(self, parameter_s='', namespaces=None):
"""Print the definition header for any callable object.
@@ -725,7 +728,7 @@ def magic_psearch(self, parameter_s=''):
except:
shell.showtraceback()
- @testdec.skip_doctest
+ @skip_doctest
def magic_who_ls(self, parameter_s=''):
"""Return a sorted list of all interactive variables.
@@ -766,7 +769,7 @@ def magic_who_ls(self, parameter_s=''):
out.sort()
return out
- @testdec.skip_doctest
+ @skip_doctest
def magic_who(self, parameter_s=''):
"""Print all interactive variables, with some minimal formatting.
@@ -827,7 +830,7 @@ def magic_who(self, parameter_s=''):
print
print
- @testdec.skip_doctest
+ @skip_doctest
def magic_whos(self, parameter_s=''):
"""Like %who, but gives some extra information about each variable.
@@ -1265,7 +1268,7 @@ def magic_debug(self, parameter_s=''):
"""
self.shell.debugger(force=True)
- @testdec.skip_doctest
+ @skip_doctest
def magic_prun(self, parameter_s ='',user_mode=1,
opts=None,arg_lst=None,prog_ns=None):
@@ -1438,7 +1441,7 @@ def magic_prun(self, parameter_s ='',user_mode=1,
else:
return None
- @testdec.skip_doctest
+ @skip_doctest
def magic_run(self, parameter_s ='',runner=None,
file_finder=get_py_filename):
"""Run the named file inside IPython as a program.
@@ -1732,7 +1735,7 @@ def magic_run(self, parameter_s ='',runner=None,
return stats
- @testdec.skip_doctest
+ @skip_doctest
def magic_timeit(self, parameter_s =''):
"""Time execution of a Python statement or expression
@@ -1869,7 +1872,7 @@ def magic_timeit(self, parameter_s =''):
if tc > tc_min:
print "Compiler time: %.2f s" % tc
- @testdec.skip_doctest
+ @skip_doctest
@needs_local_scope
def magic_time(self,parameter_s = ''):
"""Time execution of a Python statement or expression.
@@ -1963,7 +1966,7 @@ def magic_time(self,parameter_s = ''):
print "Compiler : %.2f s" % tc
return out
- @testdec.skip_doctest
+ @skip_doctest
def magic_macro(self,parameter_s = ''):
"""Define a macro for future re-execution. It accepts ranges of history,
filenames or string objects.
@@ -2020,7 +2023,6 @@ def magic_macro(self,parameter_s = ''):
'print macro_name'.
"""
-
opts,args = self.parse_options(parameter_s,'r',mode='list')
if not args: # List existing macros
return sorted(k for k,v in self.shell.user_ns.iteritems() if\
@@ -2093,6 +2095,144 @@ def magic_pastebin(self, parameter_s = ''):
pbserver = ServerProxy('http://paste.pocoo.org/xmlrpc/')
id = pbserver.pastes.newPaste("python", code)
return "http://paste.pocoo.org/show/" + id
+
+ def magic_loadpy(self, arg_s):
+ """Load a .py python script into the GUI console.
+
+ This magic command can either take a local filename or a url::
+
+ %loadpy myscript.py
+ %loadpy http://www.example.com/myscript.py
+ """
+ if not arg_s.endswith('.py'):
+ raise ValueError('%%load only works with .py files: %s' % arg_s)
+ if arg_s.startswith('http'):
+ import urllib2
+ response = urllib2.urlopen(arg_s)
+ content = response.read()
+ else:
+ content = open(arg_s).read()
+ self.set_next_input(content)
+
+ def _find_edit_target(self, args, opts, last_call):
+ """Utility method used by magic_edit to find what to edit."""
+
+ def make_filename(arg):
+ "Make a filename from the given args"
+ try:
+ filename = get_py_filename(arg)
+ except IOError:
+ # If it ends with .py but doesn't already exist, assume we want
+ # a new file.
+ if args.endswith('.py'):
+ filename = arg
+ else:
+ filename = None
+ return filename
+
+ # Set a few locals from the options for convenience:
+ opts_prev = 'p' in opts
+ opts_raw = 'r' in opts
+
+ # custom exceptions
+ class DataIsObject(Exception): pass
+
+ # Default line number value
+ lineno = opts.get('n',None)
+
+ if opts_prev:
+ args = '_%s' % last_call[0]
+ if not self.shell.user_ns.has_key(args):
+ args = last_call[1]
+
+ # use last_call to remember the state of the previous call, but don't
+ # let it be clobbered by successive '-p' calls.
+ try:
+ last_call[0] = self.shell.displayhook.prompt_count
+ if not opts_prev:
+ last_call[1] = parameter_s
+ except:
+ pass
+
+ # by default this is done with temp files, except when the given
+ # arg is a filename
+ use_temp = True
+
+ data = ''
+
+ # First, see if the arguments should be a filename.
+ filename = make_filename(args)
+ if filename:
+ use_temp = False
+ elif args:
+ # Mode where user specifies ranges of lines, like in %macro.
+ data = self.extract_input_lines(args, opts_raw)
+ if not data:
+ try:
+ # Load the parameter given as a variable. If not a string,
+ # process it as an object instead (below)
+
+ #print '*** args',args,'type',type(args) # dbg
+ data = eval(args, self.shell.user_ns)
+ if not isinstance(data, basestring):
+ raise DataIsObject
+
+ except (NameError,SyntaxError):
+ # given argument is not a variable, try as a filename
+ filename = make_filename(args)
+ if filename is None:
+ warn("Argument given (%s) can't be found as a variable "
+ "or as a filename." % args)
+ return
+ use_temp = False
+
+ except DataIsObject:
+ # macros have a special edit function
+ if isinstance(data, Macro):
+ raise MacroToEdit(data)
+
+ # For objects, try to edit the file where they are defined
+ try:
+ filename = inspect.getabsfile(data)
+ if 'fakemodule' in filename.lower() and inspect.isclass(data):
+ # class created by %edit? Try to find source
+ # by looking for method definitions instead, the
+ # __module__ in those classes is FakeModule.
+ attrs = [getattr(data, aname) for aname in dir(data)]
+ for attr in attrs:
+ if not inspect.ismethod(attr):
+ continue
+ filename = inspect.getabsfile(attr)
+ if filename and 'fakemodule' not in filename.lower():
+ # change the attribute to be the edit target instead
+ data = attr
+ break
+
+ datafile = 1
+ except TypeError:
+ filename = make_filename(args)
+ datafile = 1
+ warn('Could not find file where `%s` is defined.\n'
+ 'Opening a file named `%s`' % (args,filename))
+ # Now, make sure we can actually read the source (if it was in
+ # a temp file it's gone by now).
+ if datafile:
+ try:
+ if lineno is None:
+ lineno = inspect.getsourcelines(data)[1]
+ except IOError:
+ filename = make_filename(args)
+ if filename is None:
+ warn('The file `%s` where `%s` was defined cannot '
+ 'be read.' % (filename,data))
+ return
+ use_temp = False
+
+ if use_temp:
+ filename = self.shell.mktempfile(data)
+ print 'IPython will make a temporary file named:',filename
+
+ return filename, lineno, use_temp
def _edit_macro(self,mname,macro):
"""open an editor with the macro data in a file"""
@@ -2108,8 +2248,8 @@ def _edit_macro(self,mname,macro):
def magic_ed(self,parameter_s=''):
"""Alias to %edit."""
return self.magic_edit(parameter_s)
-
- @testdec.skip_doctest
+
+ @skip_doctest
def magic_edit(self,parameter_s='',last_call=['','']):
"""Bring up an editor and execute the resulting code.
@@ -2251,130 +2391,21 @@ def magic_edit(self,parameter_s='',last_call=['','']):
starting example for further modifications. That file also has
general instructions on how to set a new hook for use once you've
defined it."""
-
- # FIXME: This function has become a convoluted mess. It needs a
- # ground-up rewrite with clean, simple logic.
-
- def make_filename(arg):
- "Make a filename from the given args"
- try:
- filename = get_py_filename(arg)
- except IOError:
- if args.endswith('.py'):
- filename = arg
- else:
- filename = None
- return filename
-
- # custom exceptions
- class DataIsObject(Exception): pass
-
opts,args = self.parse_options(parameter_s,'prxn:')
- # Set a few locals from the options for convenience:
- opts_prev = 'p' in opts
- opts_raw = 'r' in opts
- # Default line number value
- lineno = opts.get('n',None)
-
- if opts_prev:
- args = '_%s' % last_call[0]
- if not self.shell.user_ns.has_key(args):
- args = last_call[1]
-
- # use last_call to remember the state of the previous call, but don't
- # let it be clobbered by successive '-p' calls.
try:
- last_call[0] = self.shell.displayhook.prompt_count
- if not opts_prev:
- last_call[1] = parameter_s
- except:
- pass
-
- # by default this is done with temp files, except when the given
- # arg is a filename
- use_temp = True
-
- data = ''
- if args.endswith('.py'):
- filename = make_filename(args)
- use_temp = False
- elif args:
- # Mode where user specifies ranges of lines, like in %macro.
- data = self.extract_input_lines(args, opts_raw)
- if not data:
- try:
- # Load the parameter given as a variable. If not a string,
- # process it as an object instead (below)
-
- #print '*** args',args,'type',type(args) # dbg
- data = eval(args, self.shell.user_ns)
- if not isinstance(data, basestring):
- raise DataIsObject
-
- except (NameError,SyntaxError):
- # given argument is not a variable, try as a filename
- filename = make_filename(args)
- if filename is None:
- warn("Argument given (%s) can't be found as a variable "
- "or as a filename." % args)
- return
- use_temp = False
-
- except DataIsObject:
- # macros have a special edit function
- if isinstance(data, Macro):
- self._edit_macro(args,data)
- return
-
- # For objects, try to edit the file where they are defined
- try:
- filename = inspect.getabsfile(data)
- if 'fakemodule' in filename.lower() and inspect.isclass(data):
- # class created by %edit? Try to find source
- # by looking for method definitions instead, the
- # __module__ in those classes is FakeModule.
- attrs = [getattr(data, aname) for aname in dir(data)]
- for attr in attrs:
- if not inspect.ismethod(attr):
- continue
- filename = inspect.getabsfile(attr)
- if filename and 'fakemodule' not in filename.lower():
- # change the attribute to be the edit target instead
- data = attr
- break
-
- datafile = 1
- except TypeError:
- filename = make_filename(args)
- datafile = 1
- warn('Could not find file where `%s` is defined.\n'
- 'Opening a file named `%s`' % (args,filename))
- # Now, make sure we can actually read the source (if it was in
- # a temp file it's gone by now).
- if datafile:
- try:
- if lineno is None:
- lineno = inspect.getsourcelines(data)[1]
- except IOError:
- filename = make_filename(args)
- if filename is None:
- warn('The file `%s` where `%s` was defined cannot '
- 'be read.' % (filename,data))
- return
- use_temp = False
-
- if use_temp:
- filename = self.shell.mktempfile(data)
- print 'IPython will make a temporary file named:',filename
+ filename, lineno, is_temp = self._find_edit_target(args, opts, last_call)
+ except MacroToEdit as e:
+ self._edit_macro(args, e.args[0])
+ return
# do actual editing here
print 'Editing...',
sys.stdout.flush()
try:
# Quote filenames that may have spaces in them
if ' ' in filename:
- filename = "%s" % filename
+ filename = "'%s'" % filename
self.shell.hooks.editor(filename,lineno)
except TryNext:
warn('Could not open editor')
@@ -2389,15 +2420,14 @@ class DataIsObject(Exception): pass
print
else:
print 'done. Executing edited code...'
- if opts_raw:
+ if 'r' in opts: # Untranslated IPython code
self.shell.run_cell(file_read(filename),
store_history=False)
else:
self.shell.safe_execfile(filename,self.shell.user_ns,
self.shell.user_ns)
-
- if use_temp:
+ if is_temp:
try:
return open(filename).read()
except IOError,msg:
@@ -2506,7 +2536,7 @@ def magic_pprint(self, parameter_s=''):
#......................................................................
# Functions to implement unix shell-type things
- @testdec.skip_doctest
+ @skip_doctest
def magic_alias(self, parameter_s = ''):
"""Define an alias for a system command.
@@ -2668,7 +2698,7 @@ def magic_rehashx(self, parameter_s = ''):
finally:
os.chdir(savedir)
- @testdec.skip_doctest
+ @skip_doctest
def magic_pwd(self, parameter_s = ''):
"""Return the current working directory path.
@@ -2681,7 +2711,7 @@ def magic_pwd(self, parameter_s = ''):
"""
return os.getcwd()
- @testdec.skip_doctest
+ @skip_doctest
def magic_cd(self, parameter_s=''):
"""Change the current working directory.
@@ -2886,7 +2916,7 @@ def magic_dhist(self, parameter_s=''):
header = 'Directory history (kept in _dh)',
start=ini,stop=fin)
- @testdec.skip_doctest
+ @skip_doctest
def magic_sc(self, parameter_s=''):
"""Shell capture - execute a shell command and capture its output.
@@ -3310,7 +3340,7 @@ def magic_reload_ext(self, module_str):
"""Reload an IPython extension by its module name."""
self.extension_manager.reload_extension(module_str)
- @testdec.skip_doctest
+ @skip_doctest
def magic_install_profiles(self, s):
"""Install the default IPython profiles into the .ipython dir.
@@ -3342,6 +3372,7 @@ def magic_install_profiles(self, s):
shutil.copy(src, dst)
print " %s" % f
+ @skip_doctest
def magic_install_default_config(self, s):
"""Install IPython's default config file into the .ipython dir.
@@ -3368,14 +3399,14 @@ def magic_install_default_config(self, s):
# Pylab support: simple wrappers that activate pylab, load gui input
# handling and modify slightly %run
- @testdec.skip_doctest
+ @skip_doctest
def _pylab_magic_run(self, parameter_s=''):
Magic.magic_run(self, parameter_s,
runner=mpl_runner(self.shell.safe_execfile))
_pylab_magic_run.__doc__ = magic_run.__doc__
- @testdec.skip_doctest
+ @skip_doctest
def magic_pylab(self, s):
"""Load numpy and matplotlib to work interactively.
@@ -3419,7 +3450,7 @@ def magic_tb(self, s):
See %xmode for changing exception reporting modes."""
self.shell.showtraceback()
- @testdec.skip_doctest
+ @skip_doctest
def magic_precision(self, s=''):
"""Set floating point precision for pretty printing.
View
311 IPython/core/oinspect.py
@@ -18,7 +18,6 @@
# stdlib modules
import __builtin__
-import StringIO
import inspect
import linecache
import os
@@ -75,7 +74,7 @@
'call_def', 'call_docstring',
# These won't be printed but will be used to determine how to
# format the object
- 'ismagic', 'isalias', 'argspec', 'found', 'name',
+ 'ismagic', 'isalias', 'isclass', 'argspec', 'found', 'name'
]
@@ -227,16 +226,6 @@ def call_tip(oinfo, format_call=True):
return call_line, doc
-#****************************************************************************
-# Class definitions
-
-class myStringIO(StringIO.StringIO):
- """Adds a writeln method to normal StringIO."""
- def writeln(self,*arg,**kw):
- """Does a write() and then a write('\n')"""
- self.write(*arg,**kw)
- self.write('\n')
-
class Inspector:
def __init__(self, color_table=InspectColors,
@@ -377,7 +366,41 @@ def pfile(self,obj,oname=''):
# getsourcelines returns lineno with 1-offset and page() uses
# 0-offset, so we must adjust.
page.page(self.format(open(ofile).read()),lineno-1)
-
+
+ def _format_fields(self, fields, title_width=12):
+ """Formats a list of fields for display.
+
+ Parameters
+ ----------
+ fields : list
+ A list of 2-tuples: (field_title, field_content)
+ title_width : int
+ How many characters to pad titles to. Default 12.
+ """
+ out = []
+ header = self.__head
+ for title, content in fields:
+ if len(content.splitlines()) > 1:
+ title = header(title + ":") + "\n"
+ else:
+ title = header((title+":").ljust(title_width))
+ out.append(title + content)
+ return "\n".join(out)
+
+ # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict)
+ pinfo_fields1 = [("Type", "type_name"),
+ ("Base Class", "base_class"),
+ ("String Form", "string_form"),
+ ("Namespace", "namespace"),
+ ("Length", "length"),
+ ("File", "file"),
+ ("Definition", "definition")]
+
+ pinfo_fields_obj = [("Class Docstring", "class_docstring"),
+ ("Constructor Docstring","init_docstring"),
+ ("Call def", "call_def"),
+ ("Call docstring", "call_docstring")]
+
def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0):
"""Show detailed information about an object.
@@ -392,215 +415,42 @@ def pinfo(self,obj,oname='',formatter=None,info=None,detail_level=0):
- detail_level: if set to 1, more information is given.
"""
-
- obj_type = type(obj)
-
- header = self.__head
- if info is None:
- ismagic = 0
- isalias = 0
- ospace = ''
- else:
- ismagic = info.ismagic
- isalias = info.isalias
- ospace = info.namespace
- # Get docstring, special-casing aliases:
- if isalias:
- if not callable(obj):
- try:
- ds = "Alias to the system command:\n %s" % obj[1]
- except:
- ds = "Alias: " + str(obj)
- else:
- ds = "Alias to " + str(obj)
- if obj.__doc__:
- ds += "\nDocstring:\n" + obj.__doc__
- else:
- ds = getdoc(obj)
- if ds is None:
- ds = '<no docstring>'
- if formatter is not None:
- ds = formatter(ds)
-
- # store output in a list which gets joined with \n at the end.
- out = myStringIO()
+ info = self.info(obj, oname=oname, formatter=formatter,
+ info=info, detail_level=detail_level)
+ displayfields = []
+ for title, key in self.pinfo_fields1:
+ field = info[key]
+ if field is not None:
+ displayfields.append((title, field.rstrip()))
- string_max = 200 # max size of strings to show (snipped if longer)
- shalf = int((string_max -5)/2)
-
- if ismagic:
- obj_type_name = 'Magic function'
- elif isalias:
- obj_type_name = 'System alias'
+ # Source or docstring, depending on detail level and whether
+ # source found.
+ if detail_level > 0 and info['source'] is not None:
+ displayfields.append(("Source", info['source']))
+ elif info['docstring'] is not None:
+ displayfields.append(("Docstring", info["docstring"]))
+
+ # Constructor info for classes
+ if info['isclass']:
+ if info['init_definition'] or info['init_docstring']:
+ displayfields.append(("Constructor information", ""))
+ if info['init_definition'] is not None:
+ displayfields.append((" Definition",
+ info['init_definition'].rstrip()))
+ if info['init_docstring'] is not None:
+ displayfields.append((" Docstring",
+ indent(info['init_docstring'])))
+
+ # Info for objects:
else:
- obj_type_name = obj_type.__name__
- out.writeln(header('Type:\t\t')+obj_type_name)
-
- try:
- bclass = obj.__class__
- out.writeln(header('Base Class:\t')+str(bclass))
- except: pass
-
- # String form, but snip if too long in ? form (full in ??)
- if detail_level >= self.str_detail_level:
- try:
- ostr = str(obj)
- str_head = 'String Form:'
- if not detail_level and len(ostr)>string_max:
- ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
- ostr = ("\n" + " " * len(str_head.expandtabs())).\
- join(q.strip() for q in ostr.split("\n"))
- if ostr.find('\n') > -1:
- # Print multi-line strings starting at the next line.
- str_sep = '\n'
- else:
- str_sep = '\t'
- out.writeln("%s%s%s" % (header(str_head),str_sep,ostr))
- except:
- pass
-
- if ospace:
- out.writeln(header('Namespace:\t')+ospace)
-
- # Length (for strings and lists)
- try:
- length = str(len(obj))
- out.writeln(header('Length:\t\t')+length)
- except: pass
-
- # Filename where object was defined
- binary_file = False
- try:
- try:
- fname = inspect.getabsfile(obj)
- except TypeError:
- # For an instance, the file that matters is where its class was
- # declared.
- if hasattr(obj,'__class__'):
- fname = inspect.getabsfile(obj.__class__)
- if fname.endswith('<string>'):
- fname = 'Dynamically generated function. No source code available.'
- if (fname.endswith('.so') or fname.endswith('.dll')):
- binary_file = True
- out.writeln(header('File:\t\t')+fname)
- except:
- # if anything goes wrong, we don't want to show source, so it's as
- # if the file was binary
- binary_file = True
-
- # reconstruct the function definition and print it:
- defln = self._getdef(obj,oname)
- if defln:
- out.write(header('Definition:\t')+self.format(defln))
-
- # Docstrings only in detail 0 mode, since source contains them (we
- # avoid repetitions). If source fails, we add them back, see below.
- if ds and detail_level == 0:
- out.writeln(header('Docstring:\n') + indent(ds))
-
- # Original source code for any callable
- if detail_level:
- # Flush the source cache because inspect can return out-of-date
- # source
- linecache.checkcache()
- source_success = False
- try:
- try:
- src = getsource(obj,binary_file)
- except TypeError:
- if hasattr(obj,'__class__'):
- src = getsource(obj.__class__,binary_file)
- if src is not None:
- source = self.format(src)
- out.write(header('Source:\n')+source.rstrip()+'\n')
- source_success = True
- except Exception, msg:
- pass
-
- if ds and not source_success:
- out.writeln(header('Docstring [source file open failed]:\n')
- + indent(ds))
-
- # Constructor docstring for classes
- if inspect.isclass(obj):
- # reconstruct the function definition and print it:
- try:
- obj_init = obj.__init__
- except AttributeError:
- init_def = init_ds = None
- else:
- init_def = self._getdef(obj_init,oname)
- init_ds = getdoc(obj_init)
- # Skip Python's auto-generated docstrings
- if init_ds and \
- init_ds.startswith('x.__init__(...) initializes'):
- init_ds = None
-
- if init_def or init_ds:
- out.writeln(header('Constructor information:'))
- if init_def:
- out.write(header('Definition:\t')+ self.format(init_def))
- if init_ds:
- out.writeln(header('Docstring:\n') + indent(init_ds))
- # and class docstring for instances:
- elif obj_type is types.InstanceType or \
- isinstance(obj,object):
-
- # First, check whether the instance docstring is identical to the
- # class one, and print it separately if they don't coincide. In
- # most cases they will, but it's nice to print all the info for
- # objects which use instance-customized docstrings.
- if ds:
- try:
- cls = getattr(obj,'__class__')
- except:
- class_ds = None
- else:
- class_ds = getdoc(cls)
- # Skip Python's auto-generated docstrings
- if class_ds and \
- (class_ds.startswith('function(code, globals[,') or \
- class_ds.startswith('instancemethod(function, instance,') or \
- class_ds.startswith('module(name[,') ):
- class_ds = None
- if class_ds and ds != class_ds:
- out.writeln(header('Class Docstring:\n') +
- indent(class_ds))
-
- # Next, try to show constructor docstrings
- try:
- init_ds = getdoc(obj.__init__)
- # Skip Python's auto-generated docstrings
- if init_ds and \
- init_ds.startswith('x.__init__(...) initializes'):
- init_ds = None
- except AttributeError:
- init_ds = None
- if init_ds:
- out.writeln(header('Constructor Docstring:\n') +
- indent(init_ds))
-
- # Call form docstring for callable instances
- if hasattr(obj,'__call__'):
- #out.writeln(header('Callable:\t')+'Yes')
- call_def = self._getdef(obj.__call__,oname)
- #if call_def is None:
- # out.writeln(header('Call def:\t')+
- # 'Calling definition not available.')
- if call_def is not None:
- out.writeln(header('Call def:\t')+self.format(call_def))
- call_ds = getdoc(obj.__call__)
- # Skip Python's auto-generated docstrings
- if call_ds and call_ds.startswith('x.__call__(...) <==> x(...)'):
- call_ds = None
- if call_ds:
- out.writeln(header('Call docstring:\n') + indent(call_ds))
-
- # Finally send to printer/pager
- output = out.getvalue()
- if output:
- page.page(output)
- # end pinfo
+ for title, key in self.pinfo_fields_obj:
+ field = info[key]
+ if field is not None:
+ displayfields.append((title, field.rstrip()))
+
+ # Finally send to printer/pager:
+ if displayfields:
+ page.page(self._format_fields(displayfields))
def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
"""Compute a dict with detailed information about an object.
@@ -675,11 +525,6 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
ostr = ("\n" + " " * len(str_head.expandtabs())).\
join(q.strip() for q in ostr.split("\n"))
- if ostr.find('\n') > -1:
- # Print multi-line strings starting at the next line.
- str_sep = '\n'
- else:
- str_sep = '\t'
out[str_head] = ostr
except:
pass
@@ -727,7 +572,7 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
# Flush the source cache because inspect can return out-of-date
# source
linecache.checkcache()
- source_success = False
+ source = None
try:
try:
src = getsource(obj,binary_file)
@@ -737,12 +582,16 @@ def info(self, obj, oname='', formatter=None, info=None, detail_level=0):
if src is not None:
source = self.format(src)
out['source'] = source.rstrip()
- source_success = True
- except Exception, msg:
+ except Exception:
pass
+
+ if ds and source is None:
+ out['docstring'] = ds
+