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