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