Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Config5 #400

Merged
merged 11 commits into from

3 participants

@ellisonbg
Owner

The first stage of updating our configuration system. I have added a new command line option parser that integrates with our config system. To see how all of this works, look at docs/examples/core/appconfig.py and run it like this:

python appconfig.py -h

Options can be set using the syntax:

python appconfig.py Foo.i=10 Bar.enabled=False log_level=50

This branch should be reviewed and merged into trunk and then we all can start to work on updating IPython's main apps to use this new API.

ellisonbg added some commits
@ellisonbg ellisonbg Added KeyValueConfigLoader with tests. 58fc715
@ellisonbg ellisonbg Starting help in InteractiveShell. 7a726af
@ellisonbg ellisonbg Started to move config docs to objects. 939184c
@ellisonbg ellisonbg Ongoing work on the config system.
At this point, I am mostly testing out various approaches, but:

* class_traits and class_trait_names added.
* appconfig example created for testing new config ideas.
* InteractiveShell traits updated.
* shortname handling added to command line config loader.
4999878
@ellisonbg ellisonbg Created config.application and updated Configurable. 6c3563d
@ellisonbg ellisonbg Added new tests for config.loader and configurable. e766cc8
@ellisonbg ellisonbg Added SingletonConfigurable with instance method.
I have added a singleton configurable class for objects that
should have only a single instance, such as InteractiveShell and
the main application objects. For these classes the .instance()
method should be used to create/retrieve the instance.
a8f1082
@ellisonbg ellisonbg Made InteractiveShell a SingletonConfigurable.
I have moved the instance method to a new subclass of Configurable
so we can use it on other configurable classes, like Application.
InteractiveShell now just inherits from this class.
a93fd45
@ellisonbg ellisonbg IPython.config.application.Application updates.
* Application now subclasses SingletonConfigurable.
* Added logging support.
6e90172
@ellisonbg ellisonbg Added tests for IPython.config.application.Application 605614c
@ellisonbg ellisonbg Adding more documentation to config.application related files. 4a3f8d5
@rkern

I have to say that I really dislike that InteractiveShell is currently a singleton. I don't think it's necessary, and it introduces artificial limitations. I would like to remove that, but it gets harder if we keep adding features that use it.

@ellisonbg
Owner

I am not attached to InteractiveShell being a singleton. The only class that absolutely need to be a singleton is the main Application object, which represents the entire process. In fact, two summers ago, I spent quite a bit making changes to move us in that direction. But there are still a few things we would need to figure out before we can remove this restriction entirely.

  • How to handle the cases where we are injecting things into Python's global infrastructure (sys.displayhook, sys.excepthook, sys.stdout|err|in, builtins, etc.).
  • What API to use to allow parts of the code base to get the active InteractiveShell when those parts don't hold a reference to that object.
  • How to handle references so that we can actually trigger Python's garbage collection when an InteractiveShell is done being used.

I think that fixing these things would improve the core greatly. Just out of curiosity, what usage cases do you have for multiple InteractiveShell instances?

@minrk
Owner
  • How to handle the cases where we are injecting things into Python's global infrastructure (sys.displayhook, sys.excepthook, sys.stdout|err|in, builtins, etc.).

We would have to register/unregister these overrides/redirections before/after every execution.

  • What API to use to allow parts of the code base to get the active InteractiveShell when those parts don't hold a reference to that object.

Are any of these methods not called from inside the user_ns?

  • How to handle references so that we can actually trigger Python's garbage collection when an InteractiveShell is done being used.

This is less clear to me

I would be interested in an example of having multiple simultaneous InteractiveShells in one process, because I can't think of any.

@rkern
  1. As Min says. Also, you never have to modify the __builtins__. You can always inject a crafted __builtins__ into the namespace.

  2. Give them a reference. As Min suggests, most of the current uses of InteractiveShell.instance() are injected into user_ns anyways (like the formatting display functions) or are used in the remote 0MQ process (which can remain a singleton, just with the singleton-enforcement logic moved over to that part of the code).

  3. To some extent, it'd be nice to have that problem. You don't have to solve it in order for the singletonness to be valuable, and it doesn't cause any additional problems that aren't already there. It would be nice to clean up the cycles eventually, but that can just be something that the user needs to deal with. It should be straightforward to add an InteractiveShell.finalize() that will explicitly break cycles in the object graph as a stopgap.

As for use cases, we have a CodeEditor in Traits UI that provides a shell widget that lets you run commands in a namespace. There can be any number of these in the GUI each editing different namespaces. We can use our own hacked up shell using Cmd, but IPython would be infinitely better. And before you ask, yes, IPython's Qt frontend does work just fine in-process (with some hacks around 0MQ). The only thing interfering with an IPython implementation of CodeEditor is the singleton nature of InteractiveShell.

@minrk
Owner

SingletonConfigurable does not enforce uniqueness, it just has a notion of a 'current instance'. You can create as many as you want, but cls.instance() will always refer to the same one. If we allowed InteractiveShell.select_instance(inst) to change the active instance (and return the previous), then with multiple instances, inside run_cell we could do:

saved_instance = InteractiveShell.select_instance(self)
...
InteractiveShell.select_instance(saved_instance)

That way, InteractiveShell.instance() would always return the calling instance, replacing get_ipython() in __builtins__.

Can we gather the singleton-requiring code here, so we know what needs to change?

As I understand it, there are:

  • utils.io.stdout etc. (and callers of it)
    • use shell.stdout etc. instead (easy)
  • lib.inputhook
    • I don't know, and possibly not necessary?
  • core.display_trap, core.ultratb
    • set/unset hooks in each shell.run_cell call. This is already done, as far as I can tell.
  • __builtins__
    • use InteractiveShell.instance() or require passing of instances everywhere
  • ref cleanup
    • When the Shell instance is gone, the user_ns links also go away, the HistoryManager closes, etc. What might be hanging on to references?
    • as @rkern suggested, a finalize() method might get us close enough.

What other code assumes IPython == InteractiveShell?

@rkern
  • ref cleanup: the problem is that there are (or would be) lots of references to the InteractiveShell instance creating cycles. It won't go away cleanly all the time, especially since there can be arbitrary gc-breaking objects in the user_ns. But of course, a finalize() method would break those cycles. Since InteractiveShell is a real heavyweight object that people will not be making many of, explicit finalization will probably work fine.
@minrk
Owner

Is it possible to have in the config (and traitlets) for collections of a type, along the lines of argparse's nargs. For instance, I have many Traits that are pairs of ints, but that just means I have to use a list. It would be nice if I could have something like:

class C(HasTraits):
    n = Int(n=2) # this results in a length-2 list, or 2-tuple or something
    ns = Int(n='?') # extendable list of ints

Where I would still get type checking, etc.

@ellisonbg
Owner

enthoughts.traits actually subclasses the fundamental containers (list, tuple, dict) to provide typed and traited containers for this type of thing. When I wrote traitlets, Fernando and I talked about this and decided that it is overkill for ipython and that we didn't like the idea of having to use special list/tuple/dict subclasses everywhere. I know this doesn't help your situation though. Maybe a simple way of handling it is this: set the type to List and then define an _on_foo_changed method that will get called when the trait is set. You can do the type checking at that point.

@rkern

Note that you could still let the List and Tuple traits take type arguments (e.g. List(Int) is a list of integers and Tuple(Str, Int) is a 2-tuple of a string and an integer) that only type-check on assignment. It's not quite as thorough as being able to type-check what you append to that list, but it solves most of the configuration use cases.

@ellisonbg
Owner
@minrk
Owner

@ellisonbg The old Application had a crash_handler_class. Do we not want this to be part of the top-level Application anymore?

@ellisonbg
Owner

I left out attributes and capabilities in config.application.Application that are IPython specific. In IPython.core.application, we can have a subclass with those things still there. I imagine that IPython.config could be useful outside of IPython, so I am trying to keep it clean of IPython specific logic.

@ellisonbg
Owner

This code review has gotten off topic to the issue of InteractiveShell being a singleton. I am wondering if there are other comments related to the actual config changes in this branch.

@minrk
Owner

I know, sorry about that. I'm currently working on adapting the parallel applications to the new config branch (that's where the crash handler question came from).

So far, it looks very nice and clean!

I am having some issues with init_logging. What is the right model for logging that requires other objects to be constructed first? A separate 'reinit_logging', so logging gets initialized twice?

Yes, I imagine we will want IPython.core.application that includes crash_handler, and various general-IPython bits.

@minrk
Owner

I see that you manually call init_foo(), init_bar(). Do we want a List of init methods, so we can have a single 'init' or 'construct' method that executes them in order?

Otherwise, code that wants to construct an app needs to know about all the init methods.

Or should we expect the __init__ method to produce a fully configured object?

@ellisonbg
Owner

On logging: good point. Possibly not have init call init_logging? Either that or your reinit logging (or maybe config_logging).

On calling init_foo, init_bar by hand. We want the main init method to only instantiate the app with the defaults. So init should not read the command line or config files. The reason for this is that in many cases third parties want to customize the app and not do all of the logic. An example is sympy, which may want to start ipython with command line args, but no config files. We want to have Application methods with that type of granularity. Now that doesn't mean we can't also have something like init_all() as a shorthand for "call all the init methods"

@minrk minrk commented on the diff
IPython/config/application.py
((100 lines not shown))
+
+ 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 '--h' in argv:
@minrk Owner
minrk added a note

'--help' should probably trigger here as well.

@minrk Owner
minrk added a note

That is, it should probably be '-h', '--help'. I don't think anyone expects '--' with abbreviated names.

@ellisonbg Owner

Yep

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@minrk
Owner

The logging bit is tricky, because you want some kind of logging ready as soon as __init__ fires, but sometimes the real logging configuration requires other objects to be instantiated first. I don't think there is a problem simply reassigning self.log to a differently configured logger, but the issue is that the logical name for that step ('init_logging') is taken.

I'm fine with having a custom 'really_init_logging' in my App, or pulling init_logging out of __init__, but still having the most basic possible version still fire inside __init__.

Re: init_stages, I understand the desire for breaking it up into steps, that makes sense. I am just thinking that maybe a simplest-case where there is one method that calls the steps in the right order could be part of the official API.

@ellisonbg
Owner

Yes, the logging stuff is tricky. Can we build a traitlets based API that when some attribute is set, the logging is immediately updated? Sort of like we do with the log_level.

Yes, I think we do want a single method (something other than init) that does all of the init stuff. As an aside. Previously, the individual steps in the init logic had to be done in a very specific order. Now, that is not the case. I think the traits machinery will simplify this and allow any thing to be called in a more flexible order.

@minrk
Owner

Collision detection on shortnames probably shouldn't catch inherited Traits. For instance, with:

class A(Configurable):
    a = Int(1, config=True, shortname='a')

class B(A):
    b = Int(2, config=True)

class C(A):
    c = Int(3, config=True)

No two of those classes can be used together in an Application due to a conflict on 'a'. This is a pretty big problem in the parallel code, since most objects inherit from a SessionFactory. It means that I can have no shortnames there, in one of the most commonly configured objects in the entire code.

It seems like a safe assumption that if I set 'a=5', I want that set in the parent class that defines it.

@ellisonbg
Owner

Good catch. So the collision detection code needs to see if the collision is in a parent/child class relationship and let it pass.

@minrk
Owner

Not just parent/child, but also siblings - B&C should work together. If that's too complicated, it's probably okay to require that A (the mutual ancestor) be in the class list as well, though inspecting Class.mro(), it's pretty easy to find the first common ancestor.

What precisely should the resolution be?

Case 1: A&B

  • A.a=5
  • A.a=B.a=5

Case 2: B&C

  • A.a=5
  • B.a=C.a=5
  • A.a=B.a=C.a=5

The effect of all of these is going to be the same in almost all cases. However, if there is also a D(a) class instantiated that is not in the class list, the cases that set A.a will affect D.a, and those that don't will not.

If we require that A be included in the class list, then case 2 is irrelevant.

@rkern

I would recommend against configuring the shortnames in the trait declarations themselves. The abbreviations should not be under the control of the individual components. It requires coordination between components to contribute to the global shortname namespace. Subclassing just adds more problems.

Instead, the shortname abbreviations should be owned by a top-level object, maybe the Application (I've only taken a brief look at the diffs; I'm not sure I have a complete picture, yet). For example, it could have a dictionary mapping shortnames to their full "addresses": dict(a='A.a').

This also allows the Application writer greater control rather than the component author. For example, you may write the component A and want a particular shortname for the main IPython script. I may want to use component A for some other application and do not want to emphasize that particular option.

@minrk
Owner

I think I agree about the shortnames. A simple dict of the form @rkern suggested: {'shortname' : 'Configurable.trait'} defined by Applications would make this cleaner and remove the duplicate issue entirely.

This comes up a lot in the parallel code, which has 4 scripts, all of which involve some overlap in Configurables, but have dramatically different priorities of what to configure.

The shortname dict would also allow you to organize the help output by placing the options with a shortname in a higher priority location than those without. This is possible with the current code but doesn't make any sense to do because, as Robert pointed out, the component shouldn't dictate trait prominence to the Application.

@ellisonbg
Owner

I think this makes sense as well. It will also help the design in the following way. Currently, we have to track all of the Configurables for an app and pass those to the command line parser to handle the shortnames. With this change we won't have to track all of that. Instead, we can just pass the dict. The only thing we will loose is the ability to have the auto-generated help strings for each component include the shortnames. But we can probably figure out a way of handling that without addiing to much complexity. I am in the middle of midterms through the end of next week, so I am not sure I will have much time to devote to making these changes immediately.

@minrk
Owner

Can you have the Application construct a first-priority section with just the shortname-aliased configurables?

I think it would be helpful to have the shortname options (which are presumably the high priority ones) get their own first section, followed by everything else with their full names (the current output). It's great that we now expose every configurable, but having more options makes it significantly harder for users to actually find what they are looking for. Allowing the Application to define a first-priority section with the shortnames will help alleviate that.

Is there an easy mechanism for traditional --opt/--no-opt set/unset flags for boolean configurables?

@ellisonbg
Owner

Yes, I was thinking the same thing that the shortnames doc info could go beforehand.

In terms of the --opt/--no-opt style, I don't see how we can support that easily in the current model. Also, I have never liked that style. I much prefer opt=True.

@minrk
Owner

I strongly disagree on opt=True vs. store_true/false flags, but if there's no clean way to do it, let's keep it simple.

@minrk
Owner

@ellisonbg see my macro branch for what the flags/config look like.

@ellisonbg
Owner
@minrk
Owner

They can be Config objects. There are two valid types:

  • Config objects or dicts that get passed to self.config.update
  • strings of the key-value long form: 'Class.trait=value'

The reason I allowed the strings is that probably the most common case is setting a single value (e.g. Bool traits). And it's cleaner to write enable="Bar.enabled=True" than the equivalent enable={'Bar' : {'enabled' : True}}. But both work.

I can remove it, so there's only one valid type if you prefer.

@ellisonbg
Owner

OK, thanks for clarifying this. I guess I like to "one way of doing something" idea, but I don't feel too strongly about this.

@minrk
Owner

I made the decision I was thinking it was Config or nothing - and adding Config({...}) to that is just too much. However, since dicts work fine and aren't too much more clumsy than strings, I'm okay with dropping the strings.

@ellisonbg
Owner
@minrk
Owner

I added a short summary explanation for each section, and removed the string alias, so the macros are strictly config objects or the corresponding dicts.

The current output of the appconfig example help:

$> python appconfig.py -h
This is an application.

Flags
-----
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.

--enable
    Set Bar.enabled to True
--disable
    Set Bar.enabled to False

Aliases
-------
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.

i (Foo.i) : Int
    The integer i.
running (MyApp.running) : Bool
    Is the app running?
j (Foo.j) : Int
    The integer j.
enabled (Bar.enabled) : Bool
    Enable bar.
name (Foo.name) : Unicode
    First name.

Class parameters
----------------
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]

MyApp options
-------------
MyApp.log_level : Enum
    Set the log level (0,10,20,30,40,50).
MyApp.running : Bool
    Is the app running?
MyApp.config_file : Unicode
    Load this config file

Bar options
-----------
Bar.enabled : Bool
    Enable bar.

Foo options
-----------
Foo.i : Int
    The integer i.
Foo.j : Int
    The integer j.
Foo.name : Unicode
    First name.

Should I do a PR against your Config5 branch?

What's preventing us merging this code into master now?

I already have ipcontroller and ipengine working with the new config system in a local branch. ipcontroller especially can be cleaner after merging #423. The resulting application scripts are so much cleaner now, thanks for the great work!

@ellisonbg
Owner
@minrk
Owner

PR issued. I should be available pretty much any time (except for the next ~60 minutes because I will be in transit).

@ellisonbg
Owner
@ellisonbg ellisonbg merged commit 4a3f8d5 into ipython:master
@damianavila damianavila referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 24, 2011
  1. @ellisonbg
  2. @ellisonbg
  3. @ellisonbg
  4. @ellisonbg

    Ongoing work on the config system.

    ellisonbg authored
    At this point, I am mostly testing out various approaches, but:
    
    * class_traits and class_trait_names added.
    * appconfig example created for testing new config ideas.
    * InteractiveShell traits updated.
    * shortname handling added to command line config loader.
  5. @ellisonbg
  6. @ellisonbg
  7. @ellisonbg

    Added SingletonConfigurable with instance method.

    ellisonbg authored
    I have added a singleton configurable class for objects that
    should have only a single instance, such as InteractiveShell and
    the main application objects. For these classes the .instance()
    method should be used to create/retrieve the instance.
  8. @ellisonbg

    Made InteractiveShell a SingletonConfigurable.

    ellisonbg authored
    I have moved the instance method to a new subclass of Configurable
    so we can use it on other configurable classes, like Application.
    InteractiveShell now just inherits from this class.
  9. @ellisonbg

    IPython.config.application.Application updates.

    ellisonbg authored
    * Application now subclasses SingletonConfigurable.
    * Added logging support.
  10. @ellisonbg
  11. @ellisonbg
This page is out of date. Refresh to see the latest.
View
134 IPython/config/application.py
@@ -0,0 +1,134 @@
+# 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.utils.traitlets import (
+ Unicode, List, Int, Enum
+)
+from IPython.config.loader import (
+ KeyValueConfigLoader, PyFileConfigLoader
+)
+
+#-----------------------------------------------------------------------------
+# 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.')
+
+ # A sequence of Configurable subclasses whose config=True attributes will
+ # be exposed at the command line (shortnames and help).
+ 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, shortname="log_level",
+ help="Set the log level (0,10,20,30,40,50).")
+
+ 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__)
+ 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_help(self):
+ """Print the help for each Configurable class in self.classes."""
+ 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 '--h' in argv:
@minrk Owner
minrk added a note

'--help' should probably trigger here as well.

@minrk Owner
minrk added a note

That is, it should probably be '-h', '--help'. I don't think anyone expects '--' with abbreviated names.

@ellisonbg Owner

Yep

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ self.print_description()
+ self.print_help()
+ sys.exit(1)
+
+ if '--version' in argv:
+ self.print_version()
+ sys.exit(1)
+
+ loader = KeyValueConfigLoader(argv=argv, classes=self.classes)
+ 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
105 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,102 @@ 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_shortnames(cls):
+ """Return the shortname to fullname dict for config=True traits."""
+ cls_traits = cls.class_traits(config=True)
+ shortnames = {}
+ for k, v in cls_traits.items():
+ shortname = v.get_metadata('shortname')
+ if shortname is not None:
+ longname = cls.__name__ + '.' + k
+ shortnames[shortname] = longname
+ return shortnames
+
+ @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')
+ shortname = v.get_metadata('shortname')
+ header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
+ if shortname is not None:
+ header += " (shortname=" + shortname + ")"
+ 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
118 IPython/config/loader.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-# coding: utf-8
"""A simple configuration system.
Authors
@@ -20,7 +18,6 @@
#-----------------------------------------------------------------------------
import __builtin__
-import os
import sys
from IPython.external import argparse
@@ -307,7 +304,107 @@ class CommandLineConfigLoader(ConfigLoader):
"""
+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, classes=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.
+ classes : (list, tuple) of Configurables
+ A sequence of Configurable classes that will be used to map
+ shortnames to longnames.
+
+ 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:]
+ if classes is None:
+ classes = ()
+ self.argv = argv
+ self.classes = classes
+
+ def load_config(self, argv=None, classes=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.
+ classes : (list, tuple) of Configurables
+ A sequence of Configurable classes that will be used to map
+ shortnames to longnames.
+ """
+ from IPython.config.configurable import Configurable
+
+ self.clear()
+ if argv is None:
+ argv = self.argv
+ if classes is None:
+ classes = self.classes
+
+ # Create the mapping between shortnames and longnames.
+ shortnames = {}
+ for cls in classes:
+ if issubclass(cls, Configurable):
+ sn = cls.class_get_shortnames()
+ # Check for duplicate shortnames and raise if found.
+ for k, v in sn.items():
+ if k in shortnames:
+ raise KeyError(
+ 'Duplicate shortname: both %s and %s use the shortname: "%s"' %\
+ (v, shortnames[k], k)
+ )
+ shortnames.update(sn)
+
+ for item in argv:
+ pair = tuple(item.split("="))
+ if len(pair) == 2:
+ lhs = pair[0]
+ rhs = pair[1]
+ # Substitute longnames for shortnames.
+ if lhs in shortnames:
+ lhs = shortnames[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()
+ 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 +423,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 +439,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 +450,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
90 IPython/config/tests/test_application.py
@@ -0,0 +1,90 @@
+"""
+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
+)
+
+#-----------------------------------------------------------------------------
+# Code
+#-----------------------------------------------------------------------------
+
+class Foo(Configurable):
+
+ i = Int(0, config=True, shortname='i', help="The integer i.")
+ j = Int(1, config=True, shortname='j', help="The integer j.")
+ name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
+
+
+class Bar(Configurable):
+
+ enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
+
+
+class MyApp(Application):
+
+ app_name = Unicode(u'myapp')
+ running = Bool(False, config=True, shortname="running",
+ help="Is the app running?")
+ classes = List([Bar, Foo])
+ config_file = Unicode(u'', config=True, shortname="config_file",
+ help="Load this config file")
+
+ 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)
+
View
64 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, shortname="a", help="The integer a.")
+ b = Float(1.0, config=True, shortname="b", help="The integer b.")
c = Str('no config')
+mc_help=u"""MyConfigurable options
+----------------------
+MyConfigurable.a : Int (shortname=a)
+ The integer a.
+MyConfigurable.b : Float (shortname=b)
+ The integer b."""
+
class Foo(Configurable):
- a = Int(0, config=True)
+ a = Int(0, config=True, shortname="a", 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, shortname="b", help="The string b.")
+ c = Float(config=True, shortname="c", help="The string c.")
-class TestConfigurableConfig(TestCase):
+class TestConfigurable(TestCase):
def test_default(self):
c1 = Configurable()
@@ -122,3 +134,39 @@ def test_override2(self):
self.assertEquals(c.a, 2)
self.assertEquals(c.b, 'and')
self.assertEquals(c.c, 20.0)
+
+ def test_shortnames(self):
+ sn = MyConfigurable.class_get_shortnames()
+ self.assertEquals(sn, {'a': 'MyConfigurable.a', 'b': 'MyConfigurable.b'})
+ sn = Foo.class_get_shortnames()
+ self.assertEquals(sn, {'a': 'Foo.a'})
+ sn = Bar.class_get_shortnames()
+ self.assertEquals(sn, {'a': 'Bar.a', 'c': 'Bar.c'})
+
+ 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
45 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,36 @@ 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')
+
+ def test_shortname(self):
+ class Foo(Configurable):
+ i = Int(0, config=True, shortname="i")
+ s = Unicode('hi', config=True, shortname="s")
+ cl = KeyValueConfigLoader()
+ config = cl.load_config(["i=20", "s=there"], classes=[Foo])
+ self.assertEquals(config.Foo.i, 20)
+ self.assertEquals(config.Foo.s, "there")
+
+ def test_duplicate(self):
+ class Foo(Configurable):
+ i = Int(0, config=True, shortname="i")
+ class Bar(Configurable):
+ i = Int(0, config=True, shortname="i")
+ cl = KeyValueConfigLoader()
+ self.assertRaises(KeyError, cl.load_config, ["i=20", "s=there"], classes=[Foo, Bar])
+
+
class TestConfig(TestCase):
def test_setget(self):
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)
@@ -358,31 +425,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
2  IPython/utils/tests/test_traitlets.py
@@ -366,6 +366,7 @@ class A(HasTraits):
f = Float
a = A()
self.assertEquals(a.trait_names(),['i','f'])
+ self.assertEquals(A.class_trait_names(),['i','f'])
def test_trait_metadata(self):
class A(HasTraits):
@@ -379,6 +380,7 @@ class A(HasTraits):
f = Float
a = A()
self.assertEquals(a.traits(), dict(i=A.i, f=A.f))
+ self.assertEquals(A.class_traits(), dict(i=A.i, f=A.f))
def test_traits_metadata(self):
class A(HasTraits):
View
42 IPython/utils/traitlets.py
@@ -515,6 +515,48 @@ def on_trait_change(self, handler, name=None, remove=False):
for n in names:
self._add_notifiers(handler, n)
+ @classmethod
+ def class_trait_names(cls, **metadata):
+ """Get a list of all the names of this classes traits.
+
+ This method is just like the :meth:`trait_names` method, but is unbound.
+ """
+ return cls.class_traits(**metadata).keys()
+
+ @classmethod
+ def class_traits(cls, **metadata):
+ """Get a list of all the traits of this class.
+
+ This method is just like the :meth:`traits` method, but is unbound.
+
+ The TraitTypes returned don't know anything about the values
+ that the various HasTrait's instances are holding.
+
+ This follows the same algorithm as traits does and does not allow
+ for any simple way of specifying merely that a metadata name
+ exists, but has any value. This is because get_metadata returns
+ None if a metadata key doesn't exist.
+ """
+ traits = dict([memb for memb in getmembers(cls) if \
+ isinstance(memb[1], TraitType)])
+
+ if len(metadata) == 0:
+ return traits
+
+ for meta_name, meta_eval in metadata.items():
+ if type(meta_eval) is not FunctionType:
+ metadata[meta_name] = _SimpleTest(meta_eval)
+
+ result = {}
+ for name, trait in traits.items():
+ for meta_name, meta_eval in metadata.items():
+ if not meta_eval(trait.get_metadata(meta_name)):
+ break
+ else:
+ result[name] = trait
+
+ return result
+
def trait_names(self, **metadata):
"""Get a list of all the names of this classes traits."""
return self.traits(**metadata).keys()
View
88 docs/examples/core/appconfig.py
@@ -0,0 +1,88 @@
+"""A simple example of how to use IPython.config.application.Application.
+
+This should serve as a simple example that shows how the IPython config
+system works. The main classes are:
+
+* IPython.config.configurable.Configurable
+* IPython.config.configurable.SingletonConfigurable
+* IPython.config.loader.Config
+* IPython.config.application.Application
+
+To see the command line option help, run this program from the command line::
+
+ $ python appconfig.py -h
+
+To make one of your classes configurable (from the command line and config
+files) inherit from Configurable and declare class attributes as traits (see
+classes Foo and Bar below). To make the traits configurable, you will need
+to set the following options:
+
+* ``config``: set to ``True`` to make the attribute configurable.
+* ``shortname``: by default, configurable attributes are set using the syntax
+ "Classname.attributename". At the command line, this is a bit verbose, so
+ we allow "shortnames" to be declared. Setting a shortname is optional, but
+ when you do this, you can set the option at the command line using the
+ syntax: "shortname=value".
+* ``help``: set the help string to display a help message when the ``-h``
+ option is given at the command line. The help string should be valid ReST.
+
+When the config attribute of an Application is updated, it will fire all of
+the trait's events for all of the config=True attributes.
+"""
+
+import sys
+
+from IPython.config.configurable import Configurable
+from IPython.config.application import Application
+from IPython.utils.traitlets import (
+ Bool, Unicode, Int, Float, List
+)
+
+
+class Foo(Configurable):
+ """A class that has configurable, typed attributes.
+
+ """
+
+ i = Int(0, config=True, shortname='i', help="The integer i.")
+ j = Int(1, config=True, shortname='j', help="The integer j.")
+ name = Unicode(u'Brian', config=True, shortname='name', help="First name.")
+
+
+class Bar(Configurable):
+
+ enabled = Bool(True, config=True, shortname="enabled", help="Enable bar.")
+
+
+class MyApp(Application):
+
+ app_name = Unicode(u'myapp')
+ running = Bool(False, config=True, shortname="running",
+ help="Is the app running?")
+ classes = List([Bar, Foo])
+ config_file = Unicode(u'', config=True, shortname="config_file",
+ help="Load this config file")
+
+ def init_foo(self):
+ # Pass config to other classes for them to inherit the config.
+ self.foo = Foo(config=self.config)
+
+ def init_bar(self):
+ # Pass config to other classes for them to inherit the config.
+ self.bar = Bar(config=self.config)
+
+
+
+def main():
+ app = MyApp()
+ app.parse_command_line()
+ if app.config_file:
+ app.load_config_file(app.config_file)
+ app.init_foo()
+ app.init_bar()
+ print "app.config:"
+ print app.config
+
+
+if __name__ == "__main__":
+ main()
Something went wrong with that request. Please try again.