##// END OF EJS Templates
rename macros/shortnames to flags/aliases...
MinRK -
Show More
@@ -1,231 +1,230 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import sys
23 import sys
24
24
25 from IPython.config.configurable import SingletonConfigurable
25 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.loader import (
26 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader
27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 )
28 )
29
29
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
31 Unicode, List, Int, Enum, Dict
32 )
32 )
33 from IPython.utils.text import indent
33 from IPython.utils.text import indent
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Descriptions for
36 # Descriptions for
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 macro_description = """
39 flag_description = """
40 Flags are command-line arguments passed as '--<flag>'.
40 Flags are command-line arguments passed as '--<flag>'.
41 These take no parameters, unlike regular key-value arguments.
41 These take no parameters, unlike regular key-value arguments.
42 They are typically used for setting boolean flags, or enabling
42 They are typically used for setting boolean flags, or enabling
43 modes that involve setting multiple options together.
43 modes that involve setting multiple options together.
44 """.strip() # trim newlines of front and back
44 """.strip() # trim newlines of front and back
45
45
46 shortname_description = """
46 alias_description = """
47 These are commonly set parameters, given abbreviated aliases for convenience.
47 These are commonly set parameters, given abbreviated aliases for convenience.
48 They are set in the same `name=value` way as class parameters, where
48 They are set in the same `name=value` way as class parameters, where
49 <name> is replaced by the real parameter for which it is an alias.
49 <name> is replaced by the real parameter for which it is an alias.
50 """.strip() # trim newlines of front and back
50 """.strip() # trim newlines of front and back
51
51
52 keyvalue_description = """
52 keyvalue_description = """
53 Parameters are set from command-line arguments of the form:
53 Parameters are set from command-line arguments of the form:
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
56 `C.a='range(3)'` For setting C.a=[0,1,2]
56 `C.a='range(3)'` For setting C.a=[0,1,2]
57 """.strip() # trim newlines of front and back
57 """.strip() # trim newlines of front and back
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Application class
60 # Application class
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class Application(SingletonConfigurable):
64 class Application(SingletonConfigurable):
65 """A singleton application with full configuration support."""
65 """A singleton application with full configuration support."""
66
66
67 # The name of the application, will usually match the name of the command
67 # The name of the application, will usually match the name of the command
68 # line application
68 # line application
69 app_name = Unicode(u'application')
69 app_name = Unicode(u'application')
70
70
71 # The description of the application that is printed at the beginning
71 # The description of the application that is printed at the beginning
72 # of the help.
72 # of the help.
73 description = Unicode(u'This is an application.')
73 description = Unicode(u'This is an application.')
74 # default section descriptions
74 # default section descriptions
75 macro_description = Unicode(macro_description)
75 flag_description = Unicode(flag_description)
76 shortname_description = Unicode(shortname_description)
76 alias_description = Unicode(alias_description)
77 keyvalue_description = Unicode(keyvalue_description)
77 keyvalue_description = Unicode(keyvalue_description)
78
78
79
79
80 # A sequence of Configurable subclasses whose config=True attributes will
80 # A sequence of Configurable subclasses whose config=True attributes will
81 # be exposed at the command line (shortnames and help).
81 # be exposed at the command line.
82 classes = List([])
82 classes = List([])
83
83
84 # The version string of this application.
84 # The version string of this application.
85 version = Unicode(u'0.0')
85 version = Unicode(u'0.0')
86
86
87 # The log level for the application
87 # The log level for the application
88 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
88 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
89 config=True,
89 config=True,
90 help="Set the log level (0,10,20,30,40,50).")
90 help="Set the log level (0,10,20,30,40,50).")
91
91
92 # the shortname map for configurables
92 # the alias map for configurables
93 shortnames = Dict(dict(log_level='Application.log_level'))
93 aliases = Dict(dict(log_level='Application.log_level'))
94
94
95 # macros for loading Configurables or store_const style flags
95 # flags for loading Configurables or store_const style flags
96 # macros are loaded from this dict by '--key' flags
96 # flags are loaded from this dict by '--key' flags
97 macros = Dict()
97 # this must be a dict of two-tuples, the first element being the Config/dict
98 # macro_help dict keys must match macros
98 # and the second being the help string for the flag
99 macro_help = Dict()
99 flags = Dict()
100
100
101
101
102 def __init__(self, **kwargs):
102 def __init__(self, **kwargs):
103 SingletonConfigurable.__init__(self, **kwargs)
103 SingletonConfigurable.__init__(self, **kwargs)
104 # Add my class to self.classes so my attributes appear in command line
104 # Add my class to self.classes so my attributes appear in command line
105 # options.
105 # options.
106 self.classes.insert(0, self.__class__)
106 self.classes.insert(0, self.__class__)
107
107
108 # check that macro_help has the right keys
108 # ensure self.flags dict is valid
109 # there is probably a better way to do this that doesn't use 2 dicts
109 for key,value in self.flags.iteritems():
110 keys = set(self.macros.keys())
110 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
111 badkeys = keys.difference_update(set(self.macro_help.keys()))
111 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
112 if badkeys:
112 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
113 raise KeyError("macro %r has no help in `macro_help`!"%badkeys.pop())
114 self.init_logging()
113 self.init_logging()
115
114
116 def init_logging(self):
115 def init_logging(self):
117 """Start logging for this application.
116 """Start logging for this application.
118
117
119 The default is to log to stdout using a StreaHandler. The log level
118 The default is to log to stdout using a StreaHandler. The log level
120 starts at loggin.WARN, but this can be adjusted by setting the
119 starts at loggin.WARN, but this can be adjusted by setting the
121 ``log_level`` attribute.
120 ``log_level`` attribute.
122 """
121 """
123 self.log = logging.getLogger(self.__class__.__name__)
122 self.log = logging.getLogger(self.__class__.__name__)
124 self.log.setLevel(self.log_level)
123 self.log.setLevel(self.log_level)
125 self._log_handler = logging.StreamHandler()
124 self._log_handler = logging.StreamHandler()
126 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
125 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
127 self._log_handler.setFormatter(self._log_formatter)
126 self._log_handler.setFormatter(self._log_formatter)
128 self.log.addHandler(self._log_handler)
127 self.log.addHandler(self._log_handler)
129
128
130 def _log_level_changed(self, name, old, new):
129 def _log_level_changed(self, name, old, new):
131 """Adjust the log level when log_level is set."""
130 """Adjust the log level when log_level is set."""
132 self.log.setLevel(new)
131 self.log.setLevel(new)
133
132
134 def print_shortname_help(self):
133 def print_alias_help(self):
135 """print the shortname part of the help"""
134 """print the alias part of the help"""
136 if not self.shortnames:
135 if not self.aliases:
137 return
136 return
138
137
139 print "Aliases"
138 print "Aliases"
140 print "-------"
139 print "-------"
141 print self.shortname_description
140 print self.alias_description
142 print
141 print
143
142
144 classdict = {}
143 classdict = {}
145 for c in self.classes:
144 for c in self.classes:
146 classdict[c.__name__] = c
145 classdict[c.__name__] = c
147
146
148 for shortname, longname in self.shortnames.iteritems():
147 for alias, longname in self.aliases.iteritems():
149 classname, traitname = longname.split('.',1)
148 classname, traitname = longname.split('.',1)
150 cls = classdict[classname]
149 cls = classdict[classname]
151
150
152 trait = cls.class_traits(config=True)[traitname]
151 trait = cls.class_traits(config=True)[traitname]
153 help = trait.get_metadata('help')
152 help = trait.get_metadata('help')
154 print shortname, "(%s)"%longname, ':', trait.__class__.__name__
153 print alias, "(%s)"%longname, ':', trait.__class__.__name__
155 if help:
154 if help:
156 print indent(help)
155 print indent(help)
157 print
156 print
158
157
159 def print_macro_help(self):
158 def print_flag_help(self):
160 """print the the macro part of the help"""
159 """print the flag part of the help"""
161 if not self.macros:
160 if not self.flags:
162 return
161 return
163
162
164 print "Flags"
163 print "Flags"
165 print "-----"
164 print "-----"
166 print self.macro_description
165 print self.flag_description
167 print
166 print
168
167
169 for m, cfg in self.macros.iteritems():
168 for m, (cfg,help) in self.flags.iteritems():
170 print '--'+m
169 print '--'+m
171 print indent(self.macro_help[m])
170 print indent(help)
172 print
171 print
173
172
174 def print_help(self):
173 def print_help(self):
175 """Print the help for each Configurable class in self.classes."""
174 """Print the help for each Configurable class in self.classes."""
176 self.print_macro_help()
175 self.print_flag_help()
177 self.print_shortname_help()
176 self.print_alias_help()
178 if self.classes:
177 if self.classes:
179 print "Class parameters"
178 print "Class parameters"
180 print "----------------"
179 print "----------------"
181 print self.keyvalue_description
180 print self.keyvalue_description
182 print
181 print
183
182
184 for cls in self.classes:
183 for cls in self.classes:
185 cls.class_print_help()
184 cls.class_print_help()
186 print
185 print
187
186
188 def print_description(self):
187 def print_description(self):
189 """Print the application description."""
188 """Print the application description."""
190 print self.description
189 print self.description
191 print
190 print
192
191
193 def print_version(self):
192 def print_version(self):
194 """Print the version string."""
193 """Print the version string."""
195 print self.version
194 print self.version
196
195
197 def update_config(self, config):
196 def update_config(self, config):
198 """Fire the traits events when the config is updated."""
197 """Fire the traits events when the config is updated."""
199 # Save a copy of the current config.
198 # Save a copy of the current config.
200 newconfig = deepcopy(self.config)
199 newconfig = deepcopy(self.config)
201 # Merge the new config into the current one.
200 # Merge the new config into the current one.
202 newconfig._merge(config)
201 newconfig._merge(config)
203 # Save the combined config as self.config, which triggers the traits
202 # Save the combined config as self.config, which triggers the traits
204 # events.
203 # events.
205 self.config = config
204 self.config = config
206
205
207 def parse_command_line(self, argv=None):
206 def parse_command_line(self, argv=None):
208 """Parse the command line arguments."""
207 """Parse the command line arguments."""
209 argv = sys.argv[1:] if argv is None else argv
208 argv = sys.argv[1:] if argv is None else argv
210
209
211 if '-h' in argv or '--help' in argv:
210 if '-h' in argv or '--help' in argv:
212 self.print_description()
211 self.print_description()
213 self.print_help()
212 self.print_help()
214 sys.exit(1)
213 sys.exit(1)
215
214
216 if '--version' in argv:
215 if '--version' in argv:
217 self.print_version()
216 self.print_version()
218 sys.exit(1)
217 sys.exit(1)
219
218
220 loader = KeyValueConfigLoader(argv=argv, shortnames=self.shortnames,
219 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
221 macros=self.macros)
220 flags=self.flags)
222 config = loader.load_config()
221 config = loader.load_config()
223 self.update_config(config)
222 self.update_config(config)
224
223
225 def load_config_file(self, filename, path=None):
224 def load_config_file(self, filename, path=None):
226 """Load a .py based config file by filename and path."""
225 """Load a .py based config file by filename and path."""
227 # TODO: this raises IOError if filename does not exist.
226 # TODO: this raises IOError if filename does not exist.
228 loader = PyFileConfigLoader(filename, path=path)
227 loader = PyFileConfigLoader(filename, path=path)
229 config = loader.load_config()
228 config = loader.load_config()
230 self.update_config(config)
229 self.update_config(config)
231
230
@@ -1,502 +1,501 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * Brian Granger
5 * Brian Granger
6 * Fernando Perez
6 * Fernando Perez
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2009 The IPython Development Team
10 # Copyright (C) 2008-2009 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import __builtin__
20 import __builtin__
21 import re
21 import re
22 import sys
22 import sys
23
23
24 from IPython.external import argparse
24 from IPython.external import argparse
25 from IPython.utils.path import filefind
25 from IPython.utils.path import filefind
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Exceptions
28 # Exceptions
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class ConfigError(Exception):
32 class ConfigError(Exception):
33 pass
33 pass
34
34
35
35
36 class ConfigLoaderError(ConfigError):
36 class ConfigLoaderError(ConfigError):
37 pass
37 pass
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Argparse fix
40 # Argparse fix
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 # Unfortunately argparse by default prints help messages to stderr instead of
43 # Unfortunately argparse by default prints help messages to stderr instead of
44 # stdout. This makes it annoying to capture long help screens at the command
44 # stdout. This makes it annoying to capture long help screens at the command
45 # line, since one must know how to pipe stderr, which many users don't know how
45 # line, since one must know how to pipe stderr, which many users don't know how
46 # to do. So we override the print_help method with one that defaults to
46 # to do. So we override the print_help method with one that defaults to
47 # stdout and use our class instead.
47 # stdout and use our class instead.
48
48
49 class ArgumentParser(argparse.ArgumentParser):
49 class ArgumentParser(argparse.ArgumentParser):
50 """Simple argparse subclass that prints help to stdout by default."""
50 """Simple argparse subclass that prints help to stdout by default."""
51
51
52 def print_help(self, file=None):
52 def print_help(self, file=None):
53 if file is None:
53 if file is None:
54 file = sys.stdout
54 file = sys.stdout
55 return super(ArgumentParser, self).print_help(file)
55 return super(ArgumentParser, self).print_help(file)
56
56
57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
57 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Config class for holding config information
60 # Config class for holding config information
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class Config(dict):
64 class Config(dict):
65 """An attribute based dict that can do smart merges."""
65 """An attribute based dict that can do smart merges."""
66
66
67 def __init__(self, *args, **kwds):
67 def __init__(self, *args, **kwds):
68 dict.__init__(self, *args, **kwds)
68 dict.__init__(self, *args, **kwds)
69 # This sets self.__dict__ = self, but it has to be done this way
69 # This sets self.__dict__ = self, but it has to be done this way
70 # because we are also overriding __setattr__.
70 # because we are also overriding __setattr__.
71 dict.__setattr__(self, '__dict__', self)
71 dict.__setattr__(self, '__dict__', self)
72
72
73 def _merge(self, other):
73 def _merge(self, other):
74 to_update = {}
74 to_update = {}
75 for k, v in other.iteritems():
75 for k, v in other.iteritems():
76 if not self.has_key(k):
76 if not self.has_key(k):
77 to_update[k] = v
77 to_update[k] = v
78 else: # I have this key
78 else: # I have this key
79 if isinstance(v, Config):
79 if isinstance(v, Config):
80 # Recursively merge common sub Configs
80 # Recursively merge common sub Configs
81 self[k]._merge(v)
81 self[k]._merge(v)
82 else:
82 else:
83 # Plain updates for non-Configs
83 # Plain updates for non-Configs
84 to_update[k] = v
84 to_update[k] = v
85
85
86 self.update(to_update)
86 self.update(to_update)
87
87
88 def _is_section_key(self, key):
88 def _is_section_key(self, key):
89 if key[0].upper()==key[0] and not key.startswith('_'):
89 if key[0].upper()==key[0] and not key.startswith('_'):
90 return True
90 return True
91 else:
91 else:
92 return False
92 return False
93
93
94 def __contains__(self, key):
94 def __contains__(self, key):
95 if self._is_section_key(key):
95 if self._is_section_key(key):
96 return True
96 return True
97 else:
97 else:
98 return super(Config, self).__contains__(key)
98 return super(Config, self).__contains__(key)
99 # .has_key is deprecated for dictionaries.
99 # .has_key is deprecated for dictionaries.
100 has_key = __contains__
100 has_key = __contains__
101
101
102 def _has_section(self, key):
102 def _has_section(self, key):
103 if self._is_section_key(key):
103 if self._is_section_key(key):
104 if super(Config, self).__contains__(key):
104 if super(Config, self).__contains__(key):
105 return True
105 return True
106 return False
106 return False
107
107
108 def copy(self):
108 def copy(self):
109 return type(self)(dict.copy(self))
109 return type(self)(dict.copy(self))
110
110
111 def __copy__(self):
111 def __copy__(self):
112 return self.copy()
112 return self.copy()
113
113
114 def __deepcopy__(self, memo):
114 def __deepcopy__(self, memo):
115 import copy
115 import copy
116 return type(self)(copy.deepcopy(self.items()))
116 return type(self)(copy.deepcopy(self.items()))
117
117
118 def __getitem__(self, key):
118 def __getitem__(self, key):
119 # We cannot use directly self._is_section_key, because it triggers
119 # We cannot use directly self._is_section_key, because it triggers
120 # infinite recursion on top of PyPy. Instead, we manually fish the
120 # infinite recursion on top of PyPy. Instead, we manually fish the
121 # bound method.
121 # bound method.
122 is_section_key = self.__class__._is_section_key.__get__(self)
122 is_section_key = self.__class__._is_section_key.__get__(self)
123
123
124 # Because we use this for an exec namespace, we need to delegate
124 # Because we use this for an exec namespace, we need to delegate
125 # the lookup of names in __builtin__ to itself. This means
125 # the lookup of names in __builtin__ to itself. This means
126 # that you can't have section or attribute names that are
126 # that you can't have section or attribute names that are
127 # builtins.
127 # builtins.
128 try:
128 try:
129 return getattr(__builtin__, key)
129 return getattr(__builtin__, key)
130 except AttributeError:
130 except AttributeError:
131 pass
131 pass
132 if is_section_key(key):
132 if is_section_key(key):
133 try:
133 try:
134 return dict.__getitem__(self, key)
134 return dict.__getitem__(self, key)
135 except KeyError:
135 except KeyError:
136 c = Config()
136 c = Config()
137 dict.__setitem__(self, key, c)
137 dict.__setitem__(self, key, c)
138 return c
138 return c
139 else:
139 else:
140 return dict.__getitem__(self, key)
140 return dict.__getitem__(self, key)
141
141
142 def __setitem__(self, key, value):
142 def __setitem__(self, key, value):
143 # Don't allow names in __builtin__ to be modified.
143 # Don't allow names in __builtin__ to be modified.
144 if hasattr(__builtin__, key):
144 if hasattr(__builtin__, key):
145 raise ConfigError('Config variable names cannot have the same name '
145 raise ConfigError('Config variable names cannot have the same name '
146 'as a Python builtin: %s' % key)
146 'as a Python builtin: %s' % key)
147 if self._is_section_key(key):
147 if self._is_section_key(key):
148 if not isinstance(value, Config):
148 if not isinstance(value, Config):
149 raise ValueError('values whose keys begin with an uppercase '
149 raise ValueError('values whose keys begin with an uppercase '
150 'char must be Config instances: %r, %r' % (key, value))
150 'char must be Config instances: %r, %r' % (key, value))
151 else:
151 else:
152 dict.__setitem__(self, key, value)
152 dict.__setitem__(self, key, value)
153
153
154 def __getattr__(self, key):
154 def __getattr__(self, key):
155 try:
155 try:
156 return self.__getitem__(key)
156 return self.__getitem__(key)
157 except KeyError, e:
157 except KeyError, e:
158 raise AttributeError(e)
158 raise AttributeError(e)
159
159
160 def __setattr__(self, key, value):
160 def __setattr__(self, key, value):
161 try:
161 try:
162 self.__setitem__(key, value)
162 self.__setitem__(key, value)
163 except KeyError, e:
163 except KeyError, e:
164 raise AttributeError(e)
164 raise AttributeError(e)
165
165
166 def __delattr__(self, key):
166 def __delattr__(self, key):
167 try:
167 try:
168 dict.__delitem__(self, key)
168 dict.__delitem__(self, key)
169 except KeyError, e:
169 except KeyError, e:
170 raise AttributeError(e)
170 raise AttributeError(e)
171
171
172
172
173 #-----------------------------------------------------------------------------
173 #-----------------------------------------------------------------------------
174 # Config loading classes
174 # Config loading classes
175 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
176
176
177
177
178 class ConfigLoader(object):
178 class ConfigLoader(object):
179 """A object for loading configurations from just about anywhere.
179 """A object for loading configurations from just about anywhere.
180
180
181 The resulting configuration is packaged as a :class:`Struct`.
181 The resulting configuration is packaged as a :class:`Struct`.
182
182
183 Notes
183 Notes
184 -----
184 -----
185 A :class:`ConfigLoader` does one thing: load a config from a source
185 A :class:`ConfigLoader` does one thing: load a config from a source
186 (file, command line arguments) and returns the data as a :class:`Struct`.
186 (file, command line arguments) and returns the data as a :class:`Struct`.
187 There are lots of things that :class:`ConfigLoader` does not do. It does
187 There are lots of things that :class:`ConfigLoader` does not do. It does
188 not implement complex logic for finding config files. It does not handle
188 not implement complex logic for finding config files. It does not handle
189 default values or merge multiple configs. These things need to be
189 default values or merge multiple configs. These things need to be
190 handled elsewhere.
190 handled elsewhere.
191 """
191 """
192
192
193 def __init__(self):
193 def __init__(self):
194 """A base class for config loaders.
194 """A base class for config loaders.
195
195
196 Examples
196 Examples
197 --------
197 --------
198
198
199 >>> cl = ConfigLoader()
199 >>> cl = ConfigLoader()
200 >>> config = cl.load_config()
200 >>> config = cl.load_config()
201 >>> config
201 >>> config
202 {}
202 {}
203 """
203 """
204 self.clear()
204 self.clear()
205
205
206 def clear(self):
206 def clear(self):
207 self.config = Config()
207 self.config = Config()
208
208
209 def load_config(self):
209 def load_config(self):
210 """Load a config from somewhere, return a :class:`Config` instance.
210 """Load a config from somewhere, return a :class:`Config` instance.
211
211
212 Usually, this will cause self.config to be set and then returned.
212 Usually, this will cause self.config to be set and then returned.
213 However, in most cases, :meth:`ConfigLoader.clear` should be called
213 However, in most cases, :meth:`ConfigLoader.clear` should be called
214 to erase any previous state.
214 to erase any previous state.
215 """
215 """
216 self.clear()
216 self.clear()
217 return self.config
217 return self.config
218
218
219
219
220 class FileConfigLoader(ConfigLoader):
220 class FileConfigLoader(ConfigLoader):
221 """A base class for file based configurations.
221 """A base class for file based configurations.
222
222
223 As we add more file based config loaders, the common logic should go
223 As we add more file based config loaders, the common logic should go
224 here.
224 here.
225 """
225 """
226 pass
226 pass
227
227
228
228
229 class PyFileConfigLoader(FileConfigLoader):
229 class PyFileConfigLoader(FileConfigLoader):
230 """A config loader for pure python files.
230 """A config loader for pure python files.
231
231
232 This calls execfile on a plain python file and looks for attributes
232 This calls execfile on a plain python file and looks for attributes
233 that are all caps. These attribute are added to the config Struct.
233 that are all caps. These attribute are added to the config Struct.
234 """
234 """
235
235
236 def __init__(self, filename, path=None):
236 def __init__(self, filename, path=None):
237 """Build a config loader for a filename and path.
237 """Build a config loader for a filename and path.
238
238
239 Parameters
239 Parameters
240 ----------
240 ----------
241 filename : str
241 filename : str
242 The file name of the config file.
242 The file name of the config file.
243 path : str, list, tuple
243 path : str, list, tuple
244 The path to search for the config file on, or a sequence of
244 The path to search for the config file on, or a sequence of
245 paths to try in order.
245 paths to try in order.
246 """
246 """
247 super(PyFileConfigLoader, self).__init__()
247 super(PyFileConfigLoader, self).__init__()
248 self.filename = filename
248 self.filename = filename
249 self.path = path
249 self.path = path
250 self.full_filename = ''
250 self.full_filename = ''
251 self.data = None
251 self.data = None
252
252
253 def load_config(self):
253 def load_config(self):
254 """Load the config from a file and return it as a Struct."""
254 """Load the config from a file and return it as a Struct."""
255 self.clear()
255 self.clear()
256 self._find_file()
256 self._find_file()
257 self._read_file_as_dict()
257 self._read_file_as_dict()
258 self._convert_to_config()
258 self._convert_to_config()
259 return self.config
259 return self.config
260
260
261 def _find_file(self):
261 def _find_file(self):
262 """Try to find the file by searching the paths."""
262 """Try to find the file by searching the paths."""
263 self.full_filename = filefind(self.filename, self.path)
263 self.full_filename = filefind(self.filename, self.path)
264
264
265 def _read_file_as_dict(self):
265 def _read_file_as_dict(self):
266 """Load the config file into self.config, with recursive loading."""
266 """Load the config file into self.config, with recursive loading."""
267 # This closure is made available in the namespace that is used
267 # This closure is made available in the namespace that is used
268 # to exec the config file. This allows users to call
268 # to exec the config file. This allows users to call
269 # load_subconfig('myconfig.py') to load config files recursively.
269 # load_subconfig('myconfig.py') to load config files recursively.
270 # It needs to be a closure because it has references to self.path
270 # It needs to be a closure because it has references to self.path
271 # and self.config. The sub-config is loaded with the same path
271 # and self.config. The sub-config is loaded with the same path
272 # as the parent, but it uses an empty config which is then merged
272 # as the parent, but it uses an empty config which is then merged
273 # with the parents.
273 # with the parents.
274 def load_subconfig(fname):
274 def load_subconfig(fname):
275 loader = PyFileConfigLoader(fname, self.path)
275 loader = PyFileConfigLoader(fname, self.path)
276 try:
276 try:
277 sub_config = loader.load_config()
277 sub_config = loader.load_config()
278 except IOError:
278 except IOError:
279 # Pass silently if the sub config is not there. This happens
279 # Pass silently if the sub config is not there. This happens
280 # when a user us using a profile, but not the default config.
280 # when a user us using a profile, but not the default config.
281 pass
281 pass
282 else:
282 else:
283 self.config._merge(sub_config)
283 self.config._merge(sub_config)
284
284
285 # Again, this needs to be a closure and should be used in config
285 # Again, this needs to be a closure and should be used in config
286 # files to get the config being loaded.
286 # files to get the config being loaded.
287 def get_config():
287 def get_config():
288 return self.config
288 return self.config
289
289
290 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
290 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
291 fs_encoding = sys.getfilesystemencoding() or 'ascii'
291 fs_encoding = sys.getfilesystemencoding() or 'ascii'
292 conf_filename = self.full_filename.encode(fs_encoding)
292 conf_filename = self.full_filename.encode(fs_encoding)
293 execfile(conf_filename, namespace)
293 execfile(conf_filename, namespace)
294
294
295 def _convert_to_config(self):
295 def _convert_to_config(self):
296 if self.data is None:
296 if self.data is None:
297 ConfigLoaderError('self.data does not exist')
297 ConfigLoaderError('self.data does not exist')
298
298
299
299
300 class CommandLineConfigLoader(ConfigLoader):
300 class CommandLineConfigLoader(ConfigLoader):
301 """A config loader for command line arguments.
301 """A config loader for command line arguments.
302
302
303 As we add more command line based loaders, the common logic should go
303 As we add more command line based loaders, the common logic should go
304 here.
304 here.
305 """
305 """
306
306
307 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
307 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
308 macro_pattern = re.compile(r'\-\-\w+(\-\w)*')
308 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
309
309
310 class KeyValueConfigLoader(CommandLineConfigLoader):
310 class KeyValueConfigLoader(CommandLineConfigLoader):
311 """A config loader that loads key value pairs from the command line.
311 """A config loader that loads key value pairs from the command line.
312
312
313 This allows command line options to be gives in the following form::
313 This allows command line options to be gives in the following form::
314
314
315 ipython Global.profile="foo" InteractiveShell.autocall=False
315 ipython Global.profile="foo" InteractiveShell.autocall=False
316 """
316 """
317
317
318 def __init__(self, argv=None, shortnames=None, macros=None):
318 def __init__(self, argv=None, aliases=None, flags=None):
319 """Create a key value pair config loader.
319 """Create a key value pair config loader.
320
320
321 Parameters
321 Parameters
322 ----------
322 ----------
323 argv : list
323 argv : list
324 A list that has the form of sys.argv[1:] which has unicode
324 A list that has the form of sys.argv[1:] which has unicode
325 elements of the form u"key=value". If this is None (default),
325 elements of the form u"key=value". If this is None (default),
326 then sys.argv[1:] will be used.
326 then sys.argv[1:] will be used.
327 shortnames : dict
327 aliases : dict
328 A dict of aliases for configurable traits.
328 A dict of aliases for configurable traits.
329 Keys are the short aliases, Values are the resolved trait.
329 Keys are the short aliases, Values are the resolved trait.
330 Of the form: `{'shortname' : 'Configurable.trait'}`
330 Of the form: `{'alias' : 'Configurable.trait'}`
331 macros : dict
331 flags : dict
332 A dict of macros, keyed by str name. Vaues can be Config objects,
332 A dict of flags, keyed by str name. Vaues can be Config objects,
333 dicts, or "key=value" strings. If Config or dict, when the macro
333 dicts, or "key=value" strings. If Config or dict, when the flag
334 is triggered, The macro is loaded as `self.config.update(m)`.
334 is triggered, The flag is loaded as `self.config.update(m)`.
335
335
336 Returns
336 Returns
337 -------
337 -------
338 config : Config
338 config : Config
339 The resulting Config object.
339 The resulting Config object.
340
340
341 Examples
341 Examples
342 --------
342 --------
343
343
344 >>> from IPython.config.loader import KeyValueConfigLoader
344 >>> from IPython.config.loader import KeyValueConfigLoader
345 >>> cl = KeyValueConfigLoader()
345 >>> cl = KeyValueConfigLoader()
346 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
346 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
347 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
347 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
348 """
348 """
349 if argv is None:
349 if argv is None:
350 argv = sys.argv[1:]
350 argv = sys.argv[1:]
351 self.argv = argv
351 self.argv = argv
352 self.shortnames = shortnames or {}
352 self.aliases = aliases or {}
353 self.macros = macros or {}
353 self.flags = flags or {}
354
354
355 def load_config(self, argv=None, shortnames=None, macros=None):
355 def load_config(self, argv=None, aliases=None, flags=None):
356 """Parse the configuration and generate the Config object.
356 """Parse the configuration and generate the Config object.
357
357
358 Parameters
358 Parameters
359 ----------
359 ----------
360 argv : list, optional
360 argv : list, optional
361 A list that has the form of sys.argv[1:] which has unicode
361 A list that has the form of sys.argv[1:] which has unicode
362 elements of the form u"key=value". If this is None (default),
362 elements of the form u"key=value". If this is None (default),
363 then self.argv will be used.
363 then self.argv will be used.
364 shortnames : dict
364 aliases : dict
365 A dict of aliases for configurable traits.
365 A dict of aliases for configurable traits.
366 Keys are the short aliases, Values are the resolved trait.
366 Keys are the short aliases, Values are the resolved trait.
367 Of the form: `{'shortname' : 'Configurable.trait'}`
367 Of the form: `{'alias' : 'Configurable.trait'}`
368 macros : dict
368 flags : dict
369 A dict of macros, keyed by str name. Vaues can be Config objects,
369 A dict of flags, keyed by str name. Values can be Config objects
370 dicts, or "key=value" strings. If Config or dict, when the macro
370 or dicts. When the flag is triggered, The config is loaded as
371 is triggered, The macro is loaded as `self.config.update(m)`.
371 `self.config.update(cfg)`.
372 """
372 """
373 from IPython.config.configurable import Configurable
373 from IPython.config.configurable import Configurable
374
374
375 self.clear()
375 self.clear()
376 if argv is None:
376 if argv is None:
377 argv = self.argv
377 argv = self.argv
378 if shortnames is None:
378 if aliases is None:
379 shortnames = self.shortnames
379 aliases = self.aliases
380 if macros is None:
380 if flags is None:
381 macros = self.macros
381 flags = self.flags
382
382
383 for item in argv:
383 for item in argv:
384 if kv_pattern.match(item):
384 if kv_pattern.match(item):
385 lhs,rhs = item.split('=',1)
385 lhs,rhs = item.split('=',1)
386 # Substitute longnames for shortnames.
386 # Substitute longnames for aliases.
387 if lhs in shortnames:
387 if lhs in aliases:
388 lhs = shortnames[lhs]
388 lhs = aliases[lhs]
389 exec_str = 'self.config.' + lhs + '=' + rhs
389 exec_str = 'self.config.' + lhs + '=' + rhs
390 try:
390 try:
391 # Try to see if regular Python syntax will work. This
391 # Try to see if regular Python syntax will work. This
392 # won't handle strings as the quote marks are removed
392 # won't handle strings as the quote marks are removed
393 # by the system shell.
393 # by the system shell.
394 exec exec_str in locals(), globals()
394 exec exec_str in locals(), globals()
395 except (NameError, SyntaxError):
395 except (NameError, SyntaxError):
396 # This case happens if the rhs is a string but without
396 # This case happens if the rhs is a string but without
397 # the quote marks. We add the quote marks and see if
397 # the quote marks. We add the quote marks and see if
398 # it succeeds. If it still fails, we let it raise.
398 # it succeeds. If it still fails, we let it raise.
399 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
399 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
400 exec exec_str in locals(), globals()
400 exec exec_str in locals(), globals()
401 elif macro_pattern.match(item):
401 elif flag_pattern.match(item):
402 # trim leading '--'
402 # trim leading '--'
403 m = item[2:]
403 m = item[2:]
404 macro = macros.get(m, None)
404 cfg,_ = flags.get(m, (None,None))
405 if macro is None:
405 if cfg is None:
406 raise ValueError("Unrecognized argument: %r"%item)
406 raise ValueError("Unrecognized flag: %r"%item)
407 macro = macros[m]
407 elif isinstance(cfg, (dict, Config)):
408 if isinstance(macro, (dict, Configurable)):
409 # update self.config with Config:
408 # update self.config with Config:
410 self.config.update(macros[m])
409 self.config.update(cfg)
411 else:
410 else:
412 raise ValueError("Invalid macro: %r"%macro)
411 raise ValueError("Invalid flag: %r"%flag)
413 else:
412 else:
414 raise ValueError("Invalid argument: %r"%item)
413 raise ValueError("Invalid argument: %r"%item)
415 return self.config
414 return self.config
416
415
417 class ArgParseConfigLoader(CommandLineConfigLoader):
416 class ArgParseConfigLoader(CommandLineConfigLoader):
418 """A loader that uses the argparse module to load from the command line."""
417 """A loader that uses the argparse module to load from the command line."""
419
418
420 def __init__(self, argv=None, *parser_args, **parser_kw):
419 def __init__(self, argv=None, *parser_args, **parser_kw):
421 """Create a config loader for use with argparse.
420 """Create a config loader for use with argparse.
422
421
423 Parameters
422 Parameters
424 ----------
423 ----------
425
424
426 argv : optional, list
425 argv : optional, list
427 If given, used to read command-line arguments from, otherwise
426 If given, used to read command-line arguments from, otherwise
428 sys.argv[1:] is used.
427 sys.argv[1:] is used.
429
428
430 parser_args : tuple
429 parser_args : tuple
431 A tuple of positional arguments that will be passed to the
430 A tuple of positional arguments that will be passed to the
432 constructor of :class:`argparse.ArgumentParser`.
431 constructor of :class:`argparse.ArgumentParser`.
433
432
434 parser_kw : dict
433 parser_kw : dict
435 A tuple of keyword arguments that will be passed to the
434 A tuple of keyword arguments that will be passed to the
436 constructor of :class:`argparse.ArgumentParser`.
435 constructor of :class:`argparse.ArgumentParser`.
437
436
438 Returns
437 Returns
439 -------
438 -------
440 config : Config
439 config : Config
441 The resulting Config object.
440 The resulting Config object.
442 """
441 """
443 super(CommandLineConfigLoader, self).__init__()
442 super(CommandLineConfigLoader, self).__init__()
444 if argv == None:
443 if argv == None:
445 argv = sys.argv[1:]
444 argv = sys.argv[1:]
446 self.argv = argv
445 self.argv = argv
447 self.parser_args = parser_args
446 self.parser_args = parser_args
448 self.version = parser_kw.pop("version", None)
447 self.version = parser_kw.pop("version", None)
449 kwargs = dict(argument_default=argparse.SUPPRESS)
448 kwargs = dict(argument_default=argparse.SUPPRESS)
450 kwargs.update(parser_kw)
449 kwargs.update(parser_kw)
451 self.parser_kw = kwargs
450 self.parser_kw = kwargs
452
451
453 def load_config(self, argv=None):
452 def load_config(self, argv=None):
454 """Parse command line arguments and return as a Config object.
453 """Parse command line arguments and return as a Config object.
455
454
456 Parameters
455 Parameters
457 ----------
456 ----------
458
457
459 args : optional, list
458 args : optional, list
460 If given, a list with the structure of sys.argv[1:] to parse
459 If given, a list with the structure of sys.argv[1:] to parse
461 arguments from. If not given, the instance's self.argv attribute
460 arguments from. If not given, the instance's self.argv attribute
462 (given at construction time) is used."""
461 (given at construction time) is used."""
463 self.clear()
462 self.clear()
464 if argv is None:
463 if argv is None:
465 argv = self.argv
464 argv = self.argv
466 self._create_parser()
465 self._create_parser()
467 self._parse_args(argv)
466 self._parse_args(argv)
468 self._convert_to_config()
467 self._convert_to_config()
469 return self.config
468 return self.config
470
469
471 def get_extra_args(self):
470 def get_extra_args(self):
472 if hasattr(self, 'extra_args'):
471 if hasattr(self, 'extra_args'):
473 return self.extra_args
472 return self.extra_args
474 else:
473 else:
475 return []
474 return []
476
475
477 def _create_parser(self):
476 def _create_parser(self):
478 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
477 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
479 self._add_arguments()
478 self._add_arguments()
480
479
481 def _add_arguments(self):
480 def _add_arguments(self):
482 raise NotImplementedError("subclasses must implement _add_arguments")
481 raise NotImplementedError("subclasses must implement _add_arguments")
483
482
484 def _parse_args(self, args):
483 def _parse_args(self, args):
485 """self.parser->self.parsed_data"""
484 """self.parser->self.parsed_data"""
486 # decode sys.argv to support unicode command-line options
485 # decode sys.argv to support unicode command-line options
487 uargs = []
486 uargs = []
488 for a in args:
487 for a in args:
489 if isinstance(a, str):
488 if isinstance(a, str):
490 # don't decode if we already got unicode
489 # don't decode if we already got unicode
491 a = a.decode(sys.stdin.encoding or
490 a = a.decode(sys.stdin.encoding or
492 sys.getdefaultencoding())
491 sys.getdefaultencoding())
493 uargs.append(a)
492 uargs.append(a)
494 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
493 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
495
494
496 def _convert_to_config(self):
495 def _convert_to_config(self):
497 """self.parsed_data->self.config"""
496 """self.parsed_data->self.config"""
498 for k, v in vars(self.parsed_data).iteritems():
497 for k, v in vars(self.parsed_data).iteritems():
499 exec_str = 'self.config.' + k + '= v'
498 exec_str = 'self.config.' + k + '= v'
500 exec exec_str in locals(), globals()
499 exec exec_str in locals(), globals()
501
500
502
501
@@ -1,108 +1,105 b''
1 """
1 """
2 Tests for IPython.config.application.Application
2 Tests for IPython.config.application.Application
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2011 The IPython Development Team
10 # Copyright (C) 2008-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from unittest import TestCase
20 from unittest import TestCase
21
21
22 from IPython.config.configurable import Configurable
22 from IPython.config.configurable import Configurable
23
23
24 from IPython.config.application import (
24 from IPython.config.application import (
25 Application
25 Application
26 )
26 )
27
27
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Bool, Unicode, Int, Float, List
29 Bool, Unicode, Int, Float, List, Dict
30 )
30 )
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Code
33 # Code
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class Foo(Configurable):
36 class Foo(Configurable):
37
37
38 i = Int(0, config=True, help="The integer i.")
38 i = Int(0, config=True, help="The integer i.")
39 j = Int(1, config=True, help="The integer j.")
39 j = Int(1, config=True, help="The integer j.")
40 name = Unicode(u'Brian', config=True, help="First name.")
40 name = Unicode(u'Brian', config=True, help="First name.")
41
41
42
42
43 class Bar(Configurable):
43 class Bar(Configurable):
44
44
45 enabled = Bool(True, config=True, help="Enable bar.")
45 enabled = Bool(True, config=True, help="Enable bar.")
46
46
47
47
48 class MyApp(Application):
48 class MyApp(Application):
49
49
50 app_name = Unicode(u'myapp')
50 app_name = Unicode(u'myapp')
51 running = Bool(False, config=True, shortname="running",
51 running = Bool(False, config=True,
52 help="Is the app running?")
52 help="Is the app running?")
53 classes = List([Bar, Foo])
53 classes = List([Bar, Foo])
54 config_file = Unicode(u'', config=True, shortname="config_file",
54 config_file = Unicode(u'', config=True,
55 help="Load this config file")
55 help="Load this config file")
56
56
57 shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name',
57 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name',
58 enabled='Bar.enabled', log_level='MyApp.log_level')
58 enabled='Bar.enabled', log_level='MyApp.log_level'))
59
60 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Set Bar.enabled to True"),
61 disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False")))
59
62
60 macros = dict(enable={'Bar': {'enabled' : True}}, disable={'Bar': {'enabled' : False}})
61
62 macro_help = dict(
63 enable="""Enable bar""",
64 disable="""Disable bar"""
65 )
66 def init_foo(self):
63 def init_foo(self):
67 self.foo = Foo(config=self.config)
64 self.foo = Foo(config=self.config)
68
65
69 def init_bar(self):
66 def init_bar(self):
70 self.bar = Bar(config=self.config)
67 self.bar = Bar(config=self.config)
71
68
72
69
73 class TestApplication(TestCase):
70 class TestApplication(TestCase):
74
71
75 def test_basic(self):
72 def test_basic(self):
76 app = MyApp()
73 app = MyApp()
77 self.assertEquals(app.app_name, u'myapp')
74 self.assertEquals(app.app_name, u'myapp')
78 self.assertEquals(app.running, False)
75 self.assertEquals(app.running, False)
79 self.assertEquals(app.classes, [MyApp,Bar,Foo])
76 self.assertEquals(app.classes, [MyApp,Bar,Foo])
80 self.assertEquals(app.config_file, u'')
77 self.assertEquals(app.config_file, u'')
81
78
82 def test_config(self):
79 def test_config(self):
83 app = MyApp()
80 app = MyApp()
84 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
81 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
85 config = app.config
82 config = app.config
86 self.assertEquals(config.Foo.i, 10)
83 self.assertEquals(config.Foo.i, 10)
87 self.assertEquals(config.Foo.j, 10)
84 self.assertEquals(config.Foo.j, 10)
88 self.assertEquals(config.Bar.enabled, False)
85 self.assertEquals(config.Bar.enabled, False)
89 self.assertEquals(config.MyApp.log_level,0)
86 self.assertEquals(config.MyApp.log_level,0)
90
87
91 def test_config_propagation(self):
88 def test_config_propagation(self):
92 app = MyApp()
89 app = MyApp()
93 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
90 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
94 app.init_foo()
91 app.init_foo()
95 app.init_bar()
92 app.init_bar()
96 self.assertEquals(app.foo.i, 10)
93 self.assertEquals(app.foo.i, 10)
97 self.assertEquals(app.foo.j, 10)
94 self.assertEquals(app.foo.j, 10)
98 self.assertEquals(app.bar.enabled, False)
95 self.assertEquals(app.bar.enabled, False)
99
96
100 def test_macro(self):
97 def test_alias(self):
101 app = MyApp()
98 app = MyApp()
102 app.parse_command_line(["--disable"])
99 app.parse_command_line(["--disable"])
103 app.init_bar()
100 app.init_bar()
104 self.assertEquals(app.bar.enabled, False)
101 self.assertEquals(app.bar.enabled, False)
105 app.parse_command_line(["--enable"])
102 app.parse_command_line(["--enable"])
106 app.init_bar()
103 app.init_bar()
107 self.assertEquals(app.bar.enabled, True)
104 self.assertEquals(app.bar.enabled, True)
108
105
@@ -1,98 +1,96 b''
1 """A simple example of how to use IPython.config.application.Application.
1 """A simple example of how to use IPython.config.application.Application.
2
2
3 This should serve as a simple example that shows how the IPython config
3 This should serve as a simple example that shows how the IPython config
4 system works. The main classes are:
4 system works. The main classes are:
5
5
6 * IPython.config.configurable.Configurable
6 * IPython.config.configurable.Configurable
7 * IPython.config.configurable.SingletonConfigurable
7 * IPython.config.configurable.SingletonConfigurable
8 * IPython.config.loader.Config
8 * IPython.config.loader.Config
9 * IPython.config.application.Application
9 * IPython.config.application.Application
10
10
11 To see the command line option help, run this program from the command line::
11 To see the command line option help, run this program from the command line::
12
12
13 $ python appconfig.py -h
13 $ python appconfig.py -h
14
14
15 To make one of your classes configurable (from the command line and config
15 To make one of your classes configurable (from the command line and config
16 files) inherit from Configurable and declare class attributes as traits (see
16 files) inherit from Configurable and declare class attributes as traits (see
17 classes Foo and Bar below). To make the traits configurable, you will need
17 classes Foo and Bar below). To make the traits configurable, you will need
18 to set the following options:
18 to set the following options:
19
19
20 * ``config``: set to ``True`` to make the attribute configurable.
20 * ``config``: set to ``True`` to make the attribute configurable.
21 * ``shortname``: by default, configurable attributes are set using the syntax
21 * ``shortname``: by default, configurable attributes are set using the syntax
22 "Classname.attributename". At the command line, this is a bit verbose, so
22 "Classname.attributename". At the command line, this is a bit verbose, so
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
23 we allow "shortnames" to be declared. Setting a shortname is optional, but
24 when you do this, you can set the option at the command line using the
24 when you do this, you can set the option at the command line using the
25 syntax: "shortname=value".
25 syntax: "shortname=value".
26 * ``help``: set the help string to display a help message when the ``-h``
26 * ``help``: set the help string to display a help message when the ``-h``
27 option is given at the command line. The help string should be valid ReST.
27 option is given at the command line. The help string should be valid ReST.
28
28
29 When the config attribute of an Application is updated, it will fire all of
29 When the config attribute of an Application is updated, it will fire all of
30 the trait's events for all of the config=True attributes.
30 the trait's events for all of the config=True attributes.
31 """
31 """
32
32
33 import sys
33 import sys
34
34
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.config.application import Application
36 from IPython.config.application import Application
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Bool, Unicode, Int, Float, List
38 Bool, Unicode, Int, Float, List, Dict
39 )
39 )
40
40
41
41
42 class Foo(Configurable):
42 class Foo(Configurable):
43 """A class that has configurable, typed attributes.
43 """A class that has configurable, typed attributes.
44
44
45 """
45 """
46
46
47 i = Int(0, config=True, help="The integer i.")
47 i = Int(0, config=True, help="The integer i.")
48 j = Int(1, config=True, help="The integer j.")
48 j = Int(1, config=True, help="The integer j.")
49 name = Unicode(u'Brian', config=True, help="First name.")
49 name = Unicode(u'Brian', config=True, help="First name.")
50
50
51
51
52 class Bar(Configurable):
52 class Bar(Configurable):
53
53
54 enabled = Bool(True, config=True, help="Enable bar.")
54 enabled = Bool(True, config=True, help="Enable bar.")
55
55
56
56
57 class MyApp(Application):
57 class MyApp(Application):
58
58
59 app_name = Unicode(u'myapp')
59 app_name = Unicode(u'myapp')
60 running = Bool(False, config=True,
60 running = Bool(False, config=True,
61 help="Is the app running?")
61 help="Is the app running?")
62 classes = List([Bar, Foo])
62 classes = List([Bar, Foo])
63 config_file = Unicode(u'', config=True,
63 config_file = Unicode(u'', config=True,
64 help="Load this config file")
64 help="Load this config file")
65
65
66 shortnames = dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
66 aliases = Dict(dict(i='Foo.i',j='Foo.j',name='Foo.name', running='MyApp.running',
67 enabled='Bar.enabled')
67 enabled='Bar.enabled', log_level='MyApp.log_level'))
68
69 flags = Dict(dict(enable=({'Bar': {'enabled' : True}}, "Enable Bar"),
70 disable=({'Bar': {'enabled' : False}}, "Disable Bar"),
71 debug=({'MyApp':{'log_level':10}}, "Set loglevel to DEBUG")
72 ))
68
73
69 macros = dict(enable={'Bar': {'enabled' : True}}, disable={'Bar': {'enabled' : False}})
70 macro_help = dict(
71 enable="""Set Bar.enabled to True""",
72 disable="""Set Bar.enabled to False"""
73 )
74
75 def init_foo(self):
74 def init_foo(self):
76 # Pass config to other classes for them to inherit the config.
75 # Pass config to other classes for them to inherit the config.
77 self.foo = Foo(config=self.config)
76 self.foo = Foo(config=self.config)
78
77
79 def init_bar(self):
78 def init_bar(self):
80 # Pass config to other classes for them to inherit the config.
79 # Pass config to other classes for them to inherit the config.
81 self.bar = Bar(config=self.config)
80 self.bar = Bar(config=self.config)
82
81
83
82
84
83
85 def main():
84 def main():
86 app = MyApp()
85 app = MyApp()
87 app.parse_command_line()
86 app.parse_command_line()
88 if app.config_file:
87 if app.config_file:
89 app.load_config_file(app.config_file)
88 app.load_config_file(app.config_file)
90 app.init_foo()
89 app.init_foo()
91 app.init_bar()
90 app.init_bar()
92 print "app.config:"
91 print "app.config:"
93 print app.config
92 print app.config
94 print app.bar.enabled
95
93
96
94
97 if __name__ == "__main__":
95 if __name__ == "__main__":
98 main()
96 main()
General Comments 0
You need to be logged in to leave comments. Login now