Skip to content

Commit

Permalink
Merge branch 'master' of git://github.com/ipython/ipython into fronte…
Browse files Browse the repository at this point in the history
…nd-logging
  • Loading branch information
omazapa committed May 26, 2011
2 parents 85382a1 + 5cd1ae1 commit 5809ebf
Show file tree
Hide file tree
Showing 59 changed files with 2,502 additions and 870 deletions.
230 changes: 230 additions & 0 deletions IPython/config/application.py
Original file line number Diff line number Diff line change
@@ -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)

90 changes: 88 additions & 2 deletions IPython/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


#-----------------------------------------------------------------------------
Expand All @@ -38,6 +37,9 @@ class ConfigurableError(Exception):
pass


class MultipleInstanceError(ConfigurableError):
pass

#-----------------------------------------------------------------------------
# Configurable implementation
#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -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

Loading

0 comments on commit 5809ebf

Please sign in to comment.