##// END OF EJS Templates
avoid interpreting IOError in config file as file-not-found...
MinRK -
Show More
@@ -1,436 +1,436 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 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 from copy import deepcopy
26 from copy import deepcopy
27
27
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
29 from IPython.config.loader import (
29 from IPython.config.loader import (
30 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound,
31 )
31 )
32
32
33 from IPython.utils.traitlets import (
33 from IPython.utils.traitlets import (
34 Unicode, List, Int, Enum, Dict, Instance, TraitError
34 Unicode, List, Int, Enum, Dict, Instance, TraitError
35 )
35 )
36 from IPython.utils.importstring import import_item
36 from IPython.utils.importstring import import_item
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # function for re-wrapping a helpstring
40 # function for re-wrapping a helpstring
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Descriptions for the various sections
44 # Descriptions for the various sections
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 # merge flags&aliases into options
47 # merge flags&aliases into options
48 option_description = """
48 option_description = """
49 Arguments that take values are actually convenience aliases to full
49 Arguments that take values are actually convenience aliases to full
50 Configurables, whose aliases are listed on the help line. For more information
50 Configurables, whose aliases are listed on the help line. For more information
51 on full configurables, see '--help-all'.
51 on full configurables, see '--help-all'.
52 """.strip() # trim newlines of front and back
52 """.strip() # trim newlines of front and back
53
53
54 keyvalue_description = """
54 keyvalue_description = """
55 Parameters are set from command-line arguments of the form:
55 Parameters are set from command-line arguments of the form:
56 `--Class.trait=value`.
56 `--Class.trait=value`.
57 This line is evaluated in Python, so simple expressions are allowed, e.g.::
57 This line is evaluated in Python, so simple expressions are allowed, e.g.::
58 `--C.a='range(3)'` For setting C.a=[0,1,2].
58 `--C.a='range(3)'` For setting C.a=[0,1,2].
59 """.strip() # trim newlines of front and back
59 """.strip() # trim newlines of front and back
60
60
61 subcommand_description = """
61 subcommand_description = """
62 Subcommands are launched as `{app} cmd [args]`. For information on using
62 Subcommands are launched as `{app} cmd [args]`. For information on using
63 subcommand 'cmd', do: `{app} cmd -h`.
63 subcommand 'cmd', do: `{app} cmd -h`.
64 """.strip().format(app=os.path.basename(sys.argv[0]))
64 """.strip().format(app=os.path.basename(sys.argv[0]))
65 # get running program name
65 # get running program name
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Application class
68 # Application class
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71
71
72 class ApplicationError(Exception):
72 class ApplicationError(Exception):
73 pass
73 pass
74
74
75
75
76 class Application(SingletonConfigurable):
76 class Application(SingletonConfigurable):
77 """A singleton application with full configuration support."""
77 """A singleton application with full configuration support."""
78
78
79 # The name of the application, will usually match the name of the command
79 # The name of the application, will usually match the name of the command
80 # line application
80 # line application
81 name = Unicode(u'application')
81 name = Unicode(u'application')
82
82
83 # The description of the application that is printed at the beginning
83 # The description of the application that is printed at the beginning
84 # of the help.
84 # of the help.
85 description = Unicode(u'This is an application.')
85 description = Unicode(u'This is an application.')
86 # default section descriptions
86 # default section descriptions
87 option_description = Unicode(option_description)
87 option_description = Unicode(option_description)
88 keyvalue_description = Unicode(keyvalue_description)
88 keyvalue_description = Unicode(keyvalue_description)
89 subcommand_description = Unicode(subcommand_description)
89 subcommand_description = Unicode(subcommand_description)
90
90
91 # The usage and example string that goes at the end of the help string.
91 # The usage and example string that goes at the end of the help string.
92 examples = Unicode()
92 examples = Unicode()
93
93
94 # A sequence of Configurable subclasses whose config=True attributes will
94 # A sequence of Configurable subclasses whose config=True attributes will
95 # be exposed at the command line.
95 # be exposed at the command line.
96 classes = List([])
96 classes = List([])
97
97
98 # The version string of this application.
98 # The version string of this application.
99 version = Unicode(u'0.0')
99 version = Unicode(u'0.0')
100
100
101 # The log level for the application
101 # The log level for the application
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
103 default_value=logging.WARN,
104 config=True,
104 config=True,
105 help="Set the log level by value or name.")
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
106 def _log_level_changed(self, name, old, new):
107 """Adjust the log level when log_level is set."""
107 """Adjust the log level when log_level is set."""
108 if isinstance(new, basestring):
108 if isinstance(new, basestring):
109 new = getattr(logging, new)
109 new = getattr(logging, new)
110 self.log_level = new
110 self.log_level = new
111 self.log.setLevel(new)
111 self.log.setLevel(new)
112
112
113 # the alias map for configurables
113 # the alias map for configurables
114 aliases = Dict({'log-level' : 'Application.log_level'})
114 aliases = Dict({'log-level' : 'Application.log_level'})
115
115
116 # flags for loading Configurables or store_const style flags
116 # flags for loading Configurables or store_const style flags
117 # flags are loaded from this dict by '--key' flags
117 # flags are loaded from this dict by '--key' flags
118 # this must be a dict of two-tuples, the first element being the Config/dict
118 # this must be a dict of two-tuples, the first element being the Config/dict
119 # and the second being the help string for the flag
119 # and the second being the help string for the flag
120 flags = Dict()
120 flags = Dict()
121 def _flags_changed(self, name, old, new):
121 def _flags_changed(self, name, old, new):
122 """ensure flags dict is valid"""
122 """ensure flags dict is valid"""
123 for key,value in new.iteritems():
123 for key,value in new.iteritems():
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
127
127
128
128
129 # subcommands for launching other applications
129 # subcommands for launching other applications
130 # if this is not empty, this will be a parent Application
130 # if this is not empty, this will be a parent Application
131 # this must be a dict of two-tuples,
131 # this must be a dict of two-tuples,
132 # the first element being the application class/import string
132 # the first element being the application class/import string
133 # and the second being the help string for the subcommand
133 # and the second being the help string for the subcommand
134 subcommands = Dict()
134 subcommands = Dict()
135 # parse_command_line will initialize a subapp, if requested
135 # parse_command_line will initialize a subapp, if requested
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
137
137
138 # extra command-line arguments that don't set config values
138 # extra command-line arguments that don't set config values
139 extra_args = List(Unicode)
139 extra_args = List(Unicode)
140
140
141
141
142 def __init__(self, **kwargs):
142 def __init__(self, **kwargs):
143 SingletonConfigurable.__init__(self, **kwargs)
143 SingletonConfigurable.__init__(self, **kwargs)
144 # Ensure my class is in self.classes, so my attributes appear in command line
144 # Ensure my class is in self.classes, so my attributes appear in command line
145 # options and config files.
145 # options and config files.
146 if self.__class__ not in self.classes:
146 if self.__class__ not in self.classes:
147 self.classes.insert(0, self.__class__)
147 self.classes.insert(0, self.__class__)
148
148
149 self.init_logging()
149 self.init_logging()
150
150
151 def _config_changed(self, name, old, new):
151 def _config_changed(self, name, old, new):
152 SingletonConfigurable._config_changed(self, name, old, new)
152 SingletonConfigurable._config_changed(self, name, old, new)
153 self.log.debug('Config changed:')
153 self.log.debug('Config changed:')
154 self.log.debug(repr(new))
154 self.log.debug(repr(new))
155
155
156 def init_logging(self):
156 def init_logging(self):
157 """Start logging for this application.
157 """Start logging for this application.
158
158
159 The default is to log to stdout using a StreaHandler. The log level
159 The default is to log to stdout using a StreaHandler. The log level
160 starts at loggin.WARN, but this can be adjusted by setting the
160 starts at loggin.WARN, but this can be adjusted by setting the
161 ``log_level`` attribute.
161 ``log_level`` attribute.
162 """
162 """
163 self.log = logging.getLogger(self.__class__.__name__)
163 self.log = logging.getLogger(self.__class__.__name__)
164 self.log.setLevel(self.log_level)
164 self.log.setLevel(self.log_level)
165 if sys.executable.endswith('pythonw.exe'):
165 if sys.executable.endswith('pythonw.exe'):
166 # this should really go to a file, but file-logging is only
166 # this should really go to a file, but file-logging is only
167 # hooked up in parallel applications
167 # hooked up in parallel applications
168 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
168 self._log_handler = logging.StreamHandler(open(os.devnull, 'w'))
169 else:
169 else:
170 self._log_handler = logging.StreamHandler()
170 self._log_handler = logging.StreamHandler()
171 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
171 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
172 self._log_handler.setFormatter(self._log_formatter)
172 self._log_handler.setFormatter(self._log_formatter)
173 self.log.addHandler(self._log_handler)
173 self.log.addHandler(self._log_handler)
174
174
175 def initialize(self, argv=None):
175 def initialize(self, argv=None):
176 """Do the basic steps to configure me.
176 """Do the basic steps to configure me.
177
177
178 Override in subclasses.
178 Override in subclasses.
179 """
179 """
180 self.parse_command_line(argv)
180 self.parse_command_line(argv)
181
181
182
182
183 def start(self):
183 def start(self):
184 """Start the app mainloop.
184 """Start the app mainloop.
185
185
186 Override in subclasses.
186 Override in subclasses.
187 """
187 """
188 if self.subapp is not None:
188 if self.subapp is not None:
189 return self.subapp.start()
189 return self.subapp.start()
190
190
191 def print_alias_help(self):
191 def print_alias_help(self):
192 """Print the alias part of the help."""
192 """Print the alias part of the help."""
193 if not self.aliases:
193 if not self.aliases:
194 return
194 return
195
195
196 lines = []
196 lines = []
197 classdict = {}
197 classdict = {}
198 for cls in self.classes:
198 for cls in self.classes:
199 # include all parents (up to, but excluding Configurable) in available names
199 # include all parents (up to, but excluding Configurable) in available names
200 for c in cls.mro()[:-3]:
200 for c in cls.mro()[:-3]:
201 classdict[c.__name__] = c
201 classdict[c.__name__] = c
202
202
203 for alias, longname in self.aliases.iteritems():
203 for alias, longname in self.aliases.iteritems():
204 classname, traitname = longname.split('.',1)
204 classname, traitname = longname.split('.',1)
205 cls = classdict[classname]
205 cls = classdict[classname]
206
206
207 trait = cls.class_traits(config=True)[traitname]
207 trait = cls.class_traits(config=True)[traitname]
208 help = cls.class_get_trait_help(trait).splitlines()
208 help = cls.class_get_trait_help(trait).splitlines()
209 # reformat first line
209 # reformat first line
210 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
210 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
211 if len(alias) == 1:
211 if len(alias) == 1:
212 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
212 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
213 lines.extend(help)
213 lines.extend(help)
214 # lines.append('')
214 # lines.append('')
215 print os.linesep.join(lines)
215 print os.linesep.join(lines)
216
216
217 def print_flag_help(self):
217 def print_flag_help(self):
218 """Print the flag part of the help."""
218 """Print the flag part of the help."""
219 if not self.flags:
219 if not self.flags:
220 return
220 return
221
221
222 lines = []
222 lines = []
223 for m, (cfg,help) in self.flags.iteritems():
223 for m, (cfg,help) in self.flags.iteritems():
224 prefix = '--' if len(m) > 1 else '-'
224 prefix = '--' if len(m) > 1 else '-'
225 lines.append(prefix+m)
225 lines.append(prefix+m)
226 lines.append(indent(dedent(help.strip())))
226 lines.append(indent(dedent(help.strip())))
227 # lines.append('')
227 # lines.append('')
228 print os.linesep.join(lines)
228 print os.linesep.join(lines)
229
229
230 def print_options(self):
230 def print_options(self):
231 if not self.flags and not self.aliases:
231 if not self.flags and not self.aliases:
232 return
232 return
233 lines = ['Options']
233 lines = ['Options']
234 lines.append('-'*len(lines[0]))
234 lines.append('-'*len(lines[0]))
235 lines.append('')
235 lines.append('')
236 for p in wrap_paragraphs(self.option_description):
236 for p in wrap_paragraphs(self.option_description):
237 lines.append(p)
237 lines.append(p)
238 lines.append('')
238 lines.append('')
239 print os.linesep.join(lines)
239 print os.linesep.join(lines)
240 self.print_flag_help()
240 self.print_flag_help()
241 self.print_alias_help()
241 self.print_alias_help()
242 print
242 print
243
243
244 def print_subcommands(self):
244 def print_subcommands(self):
245 """Print the subcommand part of the help."""
245 """Print the subcommand part of the help."""
246 if not self.subcommands:
246 if not self.subcommands:
247 return
247 return
248
248
249 lines = ["Subcommands"]
249 lines = ["Subcommands"]
250 lines.append('-'*len(lines[0]))
250 lines.append('-'*len(lines[0]))
251 lines.append('')
251 lines.append('')
252 for p in wrap_paragraphs(self.subcommand_description):
252 for p in wrap_paragraphs(self.subcommand_description):
253 lines.append(p)
253 lines.append(p)
254 lines.append('')
254 lines.append('')
255 for subc, (cls, help) in self.subcommands.iteritems():
255 for subc, (cls, help) in self.subcommands.iteritems():
256 lines.append(subc)
256 lines.append(subc)
257 if help:
257 if help:
258 lines.append(indent(dedent(help.strip())))
258 lines.append(indent(dedent(help.strip())))
259 lines.append('')
259 lines.append('')
260 print os.linesep.join(lines)
260 print os.linesep.join(lines)
261
261
262 def print_help(self, classes=False):
262 def print_help(self, classes=False):
263 """Print the help for each Configurable class in self.classes.
263 """Print the help for each Configurable class in self.classes.
264
264
265 If classes=False (the default), only flags and aliases are printed.
265 If classes=False (the default), only flags and aliases are printed.
266 """
266 """
267 self.print_subcommands()
267 self.print_subcommands()
268 self.print_options()
268 self.print_options()
269
269
270 if classes:
270 if classes:
271 if self.classes:
271 if self.classes:
272 print "Class parameters"
272 print "Class parameters"
273 print "----------------"
273 print "----------------"
274 print
274 print
275 for p in wrap_paragraphs(self.keyvalue_description):
275 for p in wrap_paragraphs(self.keyvalue_description):
276 print p
276 print p
277 print
277 print
278
278
279 for cls in self.classes:
279 for cls in self.classes:
280 cls.class_print_help()
280 cls.class_print_help()
281 print
281 print
282 else:
282 else:
283 print "To see all available configurables, use `--help-all`"
283 print "To see all available configurables, use `--help-all`"
284 print
284 print
285
285
286 def print_description(self):
286 def print_description(self):
287 """Print the application description."""
287 """Print the application description."""
288 for p in wrap_paragraphs(self.description):
288 for p in wrap_paragraphs(self.description):
289 print p
289 print p
290 print
290 print
291
291
292 def print_examples(self):
292 def print_examples(self):
293 """Print usage and examples.
293 """Print usage and examples.
294
294
295 This usage string goes at the end of the command line help string
295 This usage string goes at the end of the command line help string
296 and should contain examples of the application's usage.
296 and should contain examples of the application's usage.
297 """
297 """
298 if self.examples:
298 if self.examples:
299 print "Examples"
299 print "Examples"
300 print "--------"
300 print "--------"
301 print
301 print
302 print indent(dedent(self.examples.strip()))
302 print indent(dedent(self.examples.strip()))
303 print
303 print
304
304
305 def print_version(self):
305 def print_version(self):
306 """Print the version string."""
306 """Print the version string."""
307 print self.version
307 print self.version
308
308
309 def update_config(self, config):
309 def update_config(self, config):
310 """Fire the traits events when the config is updated."""
310 """Fire the traits events when the config is updated."""
311 # Save a copy of the current config.
311 # Save a copy of the current config.
312 newconfig = deepcopy(self.config)
312 newconfig = deepcopy(self.config)
313 # Merge the new config into the current one.
313 # Merge the new config into the current one.
314 newconfig._merge(config)
314 newconfig._merge(config)
315 # Save the combined config as self.config, which triggers the traits
315 # Save the combined config as self.config, which triggers the traits
316 # events.
316 # events.
317 self.config = newconfig
317 self.config = newconfig
318
318
319 def initialize_subcommand(self, subc, argv=None):
319 def initialize_subcommand(self, subc, argv=None):
320 """Initialize a subcommand with argv."""
320 """Initialize a subcommand with argv."""
321 subapp,help = self.subcommands.get(subc)
321 subapp,help = self.subcommands.get(subc)
322
322
323 if isinstance(subapp, basestring):
323 if isinstance(subapp, basestring):
324 subapp = import_item(subapp)
324 subapp = import_item(subapp)
325
325
326 # clear existing instances
326 # clear existing instances
327 self.__class__.clear_instance()
327 self.__class__.clear_instance()
328 # instantiate
328 # instantiate
329 self.subapp = subapp.instance()
329 self.subapp = subapp.instance()
330 # and initialize subapp
330 # and initialize subapp
331 self.subapp.initialize(argv)
331 self.subapp.initialize(argv)
332
332
333 def parse_command_line(self, argv=None):
333 def parse_command_line(self, argv=None):
334 """Parse the command line arguments."""
334 """Parse the command line arguments."""
335 argv = sys.argv[1:] if argv is None else argv
335 argv = sys.argv[1:] if argv is None else argv
336
336
337 if self.subcommands and len(argv) > 0:
337 if self.subcommands and len(argv) > 0:
338 # we have subcommands, and one may have been specified
338 # we have subcommands, and one may have been specified
339 subc, subargv = argv[0], argv[1:]
339 subc, subargv = argv[0], argv[1:]
340 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
340 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
341 # it's a subcommand, and *not* a flag or class parameter
341 # it's a subcommand, and *not* a flag or class parameter
342 return self.initialize_subcommand(subc, subargv)
342 return self.initialize_subcommand(subc, subargv)
343
343
344 if '-h' in argv or '--help' in argv or '--help-all' in argv:
344 if '-h' in argv or '--help' in argv or '--help-all' in argv:
345 self.print_description()
345 self.print_description()
346 self.print_help('--help-all' in argv)
346 self.print_help('--help-all' in argv)
347 self.print_examples()
347 self.print_examples()
348 self.exit(0)
348 self.exit(0)
349
349
350 if '--version' in argv:
350 if '--version' in argv:
351 self.print_version()
351 self.print_version()
352 self.exit(0)
352 self.exit(0)
353
353
354 loader = KVArgParseConfigLoader(argv=argv, aliases=self.aliases,
354 loader = KVArgParseConfigLoader(argv=argv, aliases=self.aliases,
355 flags=self.flags)
355 flags=self.flags)
356 try:
356 try:
357 config = loader.load_config()
357 config = loader.load_config()
358 self.update_config(config)
358 self.update_config(config)
359 except (TraitError, ArgumentError) as e:
359 except (TraitError, ArgumentError) as e:
360 self.print_description()
360 self.print_description()
361 self.print_help()
361 self.print_help()
362 self.print_examples()
362 self.print_examples()
363 self.log.fatal(str(e))
363 self.log.fatal(str(e))
364 self.exit(1)
364 self.exit(1)
365 # store unparsed args in extra_args
365 # store unparsed args in extra_args
366 self.extra_args = loader.extra_args
366 self.extra_args = loader.extra_args
367
367
368 def load_config_file(self, filename, path=None):
368 def load_config_file(self, filename, path=None):
369 """Load a .py based config file by filename and path."""
369 """Load a .py based config file by filename and path."""
370 loader = PyFileConfigLoader(filename, path=path)
370 loader = PyFileConfigLoader(filename, path=path)
371 try:
371 try:
372 config = loader.load_config()
372 config = loader.load_config()
373 except IOError:
373 except ConfigFileNotFound:
374 # problem with the file (probably doesn't exist), raise
374 # problem finding the file, raise
375 raise
375 raise
376 except Exception:
376 except Exception:
377 # try to get the full filename, but it will be empty in the
377 # try to get the full filename, but it will be empty in the
378 # unlikely event that the error raised before filefind finished
378 # unlikely event that the error raised before filefind finished
379 filename = loader.full_filename or filename
379 filename = loader.full_filename or filename
380 # problem while running the file
380 # problem while running the file
381 self.log.error("Exception while loading config file %s",
381 self.log.error("Exception while loading config file %s",
382 filename, exc_info=True)
382 filename, exc_info=True)
383 else:
383 else:
384 self.log.debug("Loaded config file: %s", loader.full_filename)
384 self.log.debug("Loaded config file: %s", loader.full_filename)
385 self.update_config(config)
385 self.update_config(config)
386
386
387 def generate_config_file(self):
387 def generate_config_file(self):
388 """generate default config file from Configurables"""
388 """generate default config file from Configurables"""
389 lines = ["# Configuration file for %s."%self.name]
389 lines = ["# Configuration file for %s."%self.name]
390 lines.append('')
390 lines.append('')
391 lines.append('c = get_config()')
391 lines.append('c = get_config()')
392 lines.append('')
392 lines.append('')
393 for cls in self.classes:
393 for cls in self.classes:
394 lines.append(cls.class_config_section())
394 lines.append(cls.class_config_section())
395 return '\n'.join(lines)
395 return '\n'.join(lines)
396
396
397 def exit(self, exit_status=0):
397 def exit(self, exit_status=0):
398 self.log.debug("Exiting application: %s" % self.name)
398 self.log.debug("Exiting application: %s" % self.name)
399 sys.exit(exit_status)
399 sys.exit(exit_status)
400
400
401 #-----------------------------------------------------------------------------
401 #-----------------------------------------------------------------------------
402 # utility functions, for convenience
402 # utility functions, for convenience
403 #-----------------------------------------------------------------------------
403 #-----------------------------------------------------------------------------
404
404
405 def boolean_flag(name, configurable, set_help='', unset_help=''):
405 def boolean_flag(name, configurable, set_help='', unset_help=''):
406 """Helper for building basic --trait, --no-trait flags.
406 """Helper for building basic --trait, --no-trait flags.
407
407
408 Parameters
408 Parameters
409 ----------
409 ----------
410
410
411 name : str
411 name : str
412 The name of the flag.
412 The name of the flag.
413 configurable : str
413 configurable : str
414 The 'Class.trait' string of the trait to be set/unset with the flag
414 The 'Class.trait' string of the trait to be set/unset with the flag
415 set_help : unicode
415 set_help : unicode
416 help string for --name flag
416 help string for --name flag
417 unset_help : unicode
417 unset_help : unicode
418 help string for --no-name flag
418 help string for --no-name flag
419
419
420 Returns
420 Returns
421 -------
421 -------
422
422
423 cfg : dict
423 cfg : dict
424 A dict with two keys: 'name', and 'no-name', for setting and unsetting
424 A dict with two keys: 'name', and 'no-name', for setting and unsetting
425 the trait, respectively.
425 the trait, respectively.
426 """
426 """
427 # default helpstrings
427 # default helpstrings
428 set_help = set_help or "set %s=True"%configurable
428 set_help = set_help or "set %s=True"%configurable
429 unset_help = unset_help or "set %s=False"%configurable
429 unset_help = unset_help or "set %s=False"%configurable
430
430
431 cls,trait = configurable.split('.')
431 cls,trait = configurable.split('.')
432
432
433 setter = {cls : {trait : True}}
433 setter = {cls : {trait : True}}
434 unsetter = {cls : {trait : False}}
434 unsetter = {cls : {trait : False}}
435 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
435 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
436
436
@@ -1,661 +1,666 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 * Min RK
7 * Min RK
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 import __builtin__ as builtin_mod
21 import __builtin__ as builtin_mod
22 import re
22 import re
23 import sys
23 import sys
24
24
25 from IPython.external import argparse
25 from IPython.external import argparse
26 from IPython.utils.path import filefind, get_ipython_dir
26 from IPython.utils.path import filefind, get_ipython_dir
27 from IPython.utils import py3compat, text, warn
27 from IPython.utils import py3compat, text, warn
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Exceptions
30 # Exceptions
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33
33
34 class ConfigError(Exception):
34 class ConfigError(Exception):
35 pass
35 pass
36
36
37
38 class ConfigLoaderError(ConfigError):
37 class ConfigLoaderError(ConfigError):
39 pass
38 pass
40
39
40 class ConfigFileNotFound(ConfigError):
41 pass
42
41 class ArgumentError(ConfigLoaderError):
43 class ArgumentError(ConfigLoaderError):
42 pass
44 pass
43
45
44 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
45 # Argparse fix
47 # Argparse fix
46 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
47
49
48 # Unfortunately argparse by default prints help messages to stderr instead of
50 # Unfortunately argparse by default prints help messages to stderr instead of
49 # stdout. This makes it annoying to capture long help screens at the command
51 # stdout. This makes it annoying to capture long help screens at the command
50 # line, since one must know how to pipe stderr, which many users don't know how
52 # line, since one must know how to pipe stderr, which many users don't know how
51 # to do. So we override the print_help method with one that defaults to
53 # to do. So we override the print_help method with one that defaults to
52 # stdout and use our class instead.
54 # stdout and use our class instead.
53
55
54 class ArgumentParser(argparse.ArgumentParser):
56 class ArgumentParser(argparse.ArgumentParser):
55 """Simple argparse subclass that prints help to stdout by default."""
57 """Simple argparse subclass that prints help to stdout by default."""
56
58
57 def print_help(self, file=None):
59 def print_help(self, file=None):
58 if file is None:
60 if file is None:
59 file = sys.stdout
61 file = sys.stdout
60 return super(ArgumentParser, self).print_help(file)
62 return super(ArgumentParser, self).print_help(file)
61
63
62 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
64 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
63
65
64 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
65 # Config class for holding config information
67 # Config class for holding config information
66 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
67
69
68
70
69 class Config(dict):
71 class Config(dict):
70 """An attribute based dict that can do smart merges."""
72 """An attribute based dict that can do smart merges."""
71
73
72 def __init__(self, *args, **kwds):
74 def __init__(self, *args, **kwds):
73 dict.__init__(self, *args, **kwds)
75 dict.__init__(self, *args, **kwds)
74 # This sets self.__dict__ = self, but it has to be done this way
76 # This sets self.__dict__ = self, but it has to be done this way
75 # because we are also overriding __setattr__.
77 # because we are also overriding __setattr__.
76 dict.__setattr__(self, '__dict__', self)
78 dict.__setattr__(self, '__dict__', self)
77
79
78 def _merge(self, other):
80 def _merge(self, other):
79 to_update = {}
81 to_update = {}
80 for k, v in other.iteritems():
82 for k, v in other.iteritems():
81 if not self.has_key(k):
83 if not self.has_key(k):
82 to_update[k] = v
84 to_update[k] = v
83 else: # I have this key
85 else: # I have this key
84 if isinstance(v, Config):
86 if isinstance(v, Config):
85 # Recursively merge common sub Configs
87 # Recursively merge common sub Configs
86 self[k]._merge(v)
88 self[k]._merge(v)
87 else:
89 else:
88 # Plain updates for non-Configs
90 # Plain updates for non-Configs
89 to_update[k] = v
91 to_update[k] = v
90
92
91 self.update(to_update)
93 self.update(to_update)
92
94
93 def _is_section_key(self, key):
95 def _is_section_key(self, key):
94 if key[0].upper()==key[0] and not key.startswith('_'):
96 if key[0].upper()==key[0] and not key.startswith('_'):
95 return True
97 return True
96 else:
98 else:
97 return False
99 return False
98
100
99 def __contains__(self, key):
101 def __contains__(self, key):
100 if self._is_section_key(key):
102 if self._is_section_key(key):
101 return True
103 return True
102 else:
104 else:
103 return super(Config, self).__contains__(key)
105 return super(Config, self).__contains__(key)
104 # .has_key is deprecated for dictionaries.
106 # .has_key is deprecated for dictionaries.
105 has_key = __contains__
107 has_key = __contains__
106
108
107 def _has_section(self, key):
109 def _has_section(self, key):
108 if self._is_section_key(key):
110 if self._is_section_key(key):
109 if super(Config, self).__contains__(key):
111 if super(Config, self).__contains__(key):
110 return True
112 return True
111 return False
113 return False
112
114
113 def copy(self):
115 def copy(self):
114 return type(self)(dict.copy(self))
116 return type(self)(dict.copy(self))
115
117
116 def __copy__(self):
118 def __copy__(self):
117 return self.copy()
119 return self.copy()
118
120
119 def __deepcopy__(self, memo):
121 def __deepcopy__(self, memo):
120 import copy
122 import copy
121 return type(self)(copy.deepcopy(self.items()))
123 return type(self)(copy.deepcopy(self.items()))
122
124
123 def __getitem__(self, key):
125 def __getitem__(self, key):
124 # We cannot use directly self._is_section_key, because it triggers
126 # We cannot use directly self._is_section_key, because it triggers
125 # infinite recursion on top of PyPy. Instead, we manually fish the
127 # infinite recursion on top of PyPy. Instead, we manually fish the
126 # bound method.
128 # bound method.
127 is_section_key = self.__class__._is_section_key.__get__(self)
129 is_section_key = self.__class__._is_section_key.__get__(self)
128
130
129 # Because we use this for an exec namespace, we need to delegate
131 # Because we use this for an exec namespace, we need to delegate
130 # the lookup of names in __builtin__ to itself. This means
132 # the lookup of names in __builtin__ to itself. This means
131 # that you can't have section or attribute names that are
133 # that you can't have section or attribute names that are
132 # builtins.
134 # builtins.
133 try:
135 try:
134 return getattr(builtin_mod, key)
136 return getattr(builtin_mod, key)
135 except AttributeError:
137 except AttributeError:
136 pass
138 pass
137 if is_section_key(key):
139 if is_section_key(key):
138 try:
140 try:
139 return dict.__getitem__(self, key)
141 return dict.__getitem__(self, key)
140 except KeyError:
142 except KeyError:
141 c = Config()
143 c = Config()
142 dict.__setitem__(self, key, c)
144 dict.__setitem__(self, key, c)
143 return c
145 return c
144 else:
146 else:
145 return dict.__getitem__(self, key)
147 return dict.__getitem__(self, key)
146
148
147 def __setitem__(self, key, value):
149 def __setitem__(self, key, value):
148 # Don't allow names in __builtin__ to be modified.
150 # Don't allow names in __builtin__ to be modified.
149 if hasattr(builtin_mod, key):
151 if hasattr(builtin_mod, key):
150 raise ConfigError('Config variable names cannot have the same name '
152 raise ConfigError('Config variable names cannot have the same name '
151 'as a Python builtin: %s' % key)
153 'as a Python builtin: %s' % key)
152 if self._is_section_key(key):
154 if self._is_section_key(key):
153 if not isinstance(value, Config):
155 if not isinstance(value, Config):
154 raise ValueError('values whose keys begin with an uppercase '
156 raise ValueError('values whose keys begin with an uppercase '
155 'char must be Config instances: %r, %r' % (key, value))
157 'char must be Config instances: %r, %r' % (key, value))
156 else:
158 else:
157 dict.__setitem__(self, key, value)
159 dict.__setitem__(self, key, value)
158
160
159 def __getattr__(self, key):
161 def __getattr__(self, key):
160 try:
162 try:
161 return self.__getitem__(key)
163 return self.__getitem__(key)
162 except KeyError, e:
164 except KeyError, e:
163 raise AttributeError(e)
165 raise AttributeError(e)
164
166
165 def __setattr__(self, key, value):
167 def __setattr__(self, key, value):
166 try:
168 try:
167 self.__setitem__(key, value)
169 self.__setitem__(key, value)
168 except KeyError, e:
170 except KeyError, e:
169 raise AttributeError(e)
171 raise AttributeError(e)
170
172
171 def __delattr__(self, key):
173 def __delattr__(self, key):
172 try:
174 try:
173 dict.__delitem__(self, key)
175 dict.__delitem__(self, key)
174 except KeyError, e:
176 except KeyError, e:
175 raise AttributeError(e)
177 raise AttributeError(e)
176
178
177
179
178 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
179 # Config loading classes
181 # Config loading classes
180 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
181
183
182
184
183 class ConfigLoader(object):
185 class ConfigLoader(object):
184 """A object for loading configurations from just about anywhere.
186 """A object for loading configurations from just about anywhere.
185
187
186 The resulting configuration is packaged as a :class:`Struct`.
188 The resulting configuration is packaged as a :class:`Struct`.
187
189
188 Notes
190 Notes
189 -----
191 -----
190 A :class:`ConfigLoader` does one thing: load a config from a source
192 A :class:`ConfigLoader` does one thing: load a config from a source
191 (file, command line arguments) and returns the data as a :class:`Struct`.
193 (file, command line arguments) and returns the data as a :class:`Struct`.
192 There are lots of things that :class:`ConfigLoader` does not do. It does
194 There are lots of things that :class:`ConfigLoader` does not do. It does
193 not implement complex logic for finding config files. It does not handle
195 not implement complex logic for finding config files. It does not handle
194 default values or merge multiple configs. These things need to be
196 default values or merge multiple configs. These things need to be
195 handled elsewhere.
197 handled elsewhere.
196 """
198 """
197
199
198 def __init__(self):
200 def __init__(self):
199 """A base class for config loaders.
201 """A base class for config loaders.
200
202
201 Examples
203 Examples
202 --------
204 --------
203
205
204 >>> cl = ConfigLoader()
206 >>> cl = ConfigLoader()
205 >>> config = cl.load_config()
207 >>> config = cl.load_config()
206 >>> config
208 >>> config
207 {}
209 {}
208 """
210 """
209 self.clear()
211 self.clear()
210
212
211 def clear(self):
213 def clear(self):
212 self.config = Config()
214 self.config = Config()
213
215
214 def load_config(self):
216 def load_config(self):
215 """Load a config from somewhere, return a :class:`Config` instance.
217 """Load a config from somewhere, return a :class:`Config` instance.
216
218
217 Usually, this will cause self.config to be set and then returned.
219 Usually, this will cause self.config to be set and then returned.
218 However, in most cases, :meth:`ConfigLoader.clear` should be called
220 However, in most cases, :meth:`ConfigLoader.clear` should be called
219 to erase any previous state.
221 to erase any previous state.
220 """
222 """
221 self.clear()
223 self.clear()
222 return self.config
224 return self.config
223
225
224
226
225 class FileConfigLoader(ConfigLoader):
227 class FileConfigLoader(ConfigLoader):
226 """A base class for file based configurations.
228 """A base class for file based configurations.
227
229
228 As we add more file based config loaders, the common logic should go
230 As we add more file based config loaders, the common logic should go
229 here.
231 here.
230 """
232 """
231 pass
233 pass
232
234
233
235
234 class PyFileConfigLoader(FileConfigLoader):
236 class PyFileConfigLoader(FileConfigLoader):
235 """A config loader for pure python files.
237 """A config loader for pure python files.
236
238
237 This calls execfile on a plain python file and looks for attributes
239 This calls execfile on a plain python file and looks for attributes
238 that are all caps. These attribute are added to the config Struct.
240 that are all caps. These attribute are added to the config Struct.
239 """
241 """
240
242
241 def __init__(self, filename, path=None):
243 def __init__(self, filename, path=None):
242 """Build a config loader for a filename and path.
244 """Build a config loader for a filename and path.
243
245
244 Parameters
246 Parameters
245 ----------
247 ----------
246 filename : str
248 filename : str
247 The file name of the config file.
249 The file name of the config file.
248 path : str, list, tuple
250 path : str, list, tuple
249 The path to search for the config file on, or a sequence of
251 The path to search for the config file on, or a sequence of
250 paths to try in order.
252 paths to try in order.
251 """
253 """
252 super(PyFileConfigLoader, self).__init__()
254 super(PyFileConfigLoader, self).__init__()
253 self.filename = filename
255 self.filename = filename
254 self.path = path
256 self.path = path
255 self.full_filename = ''
257 self.full_filename = ''
256 self.data = None
258 self.data = None
257
259
258 def load_config(self):
260 def load_config(self):
259 """Load the config from a file and return it as a Struct."""
261 """Load the config from a file and return it as a Struct."""
260 self.clear()
262 self.clear()
261 self._find_file()
263 try:
264 self._find_file()
265 except IOError as e:
266 raise ConfigFileNotFound(str(e))
262 self._read_file_as_dict()
267 self._read_file_as_dict()
263 self._convert_to_config()
268 self._convert_to_config()
264 return self.config
269 return self.config
265
270
266 def _find_file(self):
271 def _find_file(self):
267 """Try to find the file by searching the paths."""
272 """Try to find the file by searching the paths."""
268 self.full_filename = filefind(self.filename, self.path)
273 self.full_filename = filefind(self.filename, self.path)
269
274
270 def _read_file_as_dict(self):
275 def _read_file_as_dict(self):
271 """Load the config file into self.config, with recursive loading."""
276 """Load the config file into self.config, with recursive loading."""
272 # This closure is made available in the namespace that is used
277 # This closure is made available in the namespace that is used
273 # to exec the config file. It allows users to call
278 # to exec the config file. It allows users to call
274 # load_subconfig('myconfig.py') to load config files recursively.
279 # load_subconfig('myconfig.py') to load config files recursively.
275 # It needs to be a closure because it has references to self.path
280 # It needs to be a closure because it has references to self.path
276 # and self.config. The sub-config is loaded with the same path
281 # and self.config. The sub-config is loaded with the same path
277 # as the parent, but it uses an empty config which is then merged
282 # as the parent, but it uses an empty config which is then merged
278 # with the parents.
283 # with the parents.
279
284
280 # If a profile is specified, the config file will be loaded
285 # If a profile is specified, the config file will be loaded
281 # from that profile
286 # from that profile
282
287
283 def load_subconfig(fname, profile=None):
288 def load_subconfig(fname, profile=None):
284 # import here to prevent circular imports
289 # import here to prevent circular imports
285 from IPython.core.profiledir import ProfileDir, ProfileDirError
290 from IPython.core.profiledir import ProfileDir, ProfileDirError
286 if profile is not None:
291 if profile is not None:
287 try:
292 try:
288 profile_dir = ProfileDir.find_profile_dir_by_name(
293 profile_dir = ProfileDir.find_profile_dir_by_name(
289 get_ipython_dir(),
294 get_ipython_dir(),
290 profile,
295 profile,
291 )
296 )
292 except ProfileDirError:
297 except ProfileDirError:
293 return
298 return
294 path = profile_dir.location
299 path = profile_dir.location
295 else:
300 else:
296 path = self.path
301 path = self.path
297 loader = PyFileConfigLoader(fname, path)
302 loader = PyFileConfigLoader(fname, path)
298 try:
303 try:
299 sub_config = loader.load_config()
304 sub_config = loader.load_config()
300 except IOError:
305 except ConfigFileNotFound:
301 # Pass silently if the sub config is not there. This happens
306 # Pass silently if the sub config is not there. This happens
302 # when a user s using a profile, but not the default config.
307 # when a user s using a profile, but not the default config.
303 pass
308 pass
304 else:
309 else:
305 self.config._merge(sub_config)
310 self.config._merge(sub_config)
306
311
307 # Again, this needs to be a closure and should be used in config
312 # Again, this needs to be a closure and should be used in config
308 # files to get the config being loaded.
313 # files to get the config being loaded.
309 def get_config():
314 def get_config():
310 return self.config
315 return self.config
311
316
312 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
317 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
313 fs_encoding = sys.getfilesystemencoding() or 'ascii'
318 fs_encoding = sys.getfilesystemencoding() or 'ascii'
314 conf_filename = self.full_filename.encode(fs_encoding)
319 conf_filename = self.full_filename.encode(fs_encoding)
315 py3compat.execfile(conf_filename, namespace)
320 py3compat.execfile(conf_filename, namespace)
316
321
317 def _convert_to_config(self):
322 def _convert_to_config(self):
318 if self.data is None:
323 if self.data is None:
319 ConfigLoaderError('self.data does not exist')
324 ConfigLoaderError('self.data does not exist')
320
325
321
326
322 class CommandLineConfigLoader(ConfigLoader):
327 class CommandLineConfigLoader(ConfigLoader):
323 """A config loader for command line arguments.
328 """A config loader for command line arguments.
324
329
325 As we add more command line based loaders, the common logic should go
330 As we add more command line based loaders, the common logic should go
326 here.
331 here.
327 """
332 """
328
333
329 def _exec_config_str(self, lhs, rhs):
334 def _exec_config_str(self, lhs, rhs):
330 exec_str = 'self.config.' + lhs + '=' + rhs
335 exec_str = 'self.config.' + lhs + '=' + rhs
331 try:
336 try:
332 # Try to see if regular Python syntax will work. This
337 # Try to see if regular Python syntax will work. This
333 # won't handle strings as the quote marks are removed
338 # won't handle strings as the quote marks are removed
334 # by the system shell.
339 # by the system shell.
335 exec exec_str in locals(), globals()
340 exec exec_str in locals(), globals()
336 except (NameError, SyntaxError):
341 except (NameError, SyntaxError):
337 # This case happens if the rhs is a string but without
342 # This case happens if the rhs is a string but without
338 # the quote marks. Use repr, to get quote marks, and
343 # the quote marks. Use repr, to get quote marks, and
339 # 'u' prefix and see if
344 # 'u' prefix and see if
340 # it succeeds. If it still fails, we let it raise.
345 # it succeeds. If it still fails, we let it raise.
341 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
346 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
342 exec exec_str in locals(), globals()
347 exec exec_str in locals(), globals()
343
348
344 def _load_flag(self, cfg):
349 def _load_flag(self, cfg):
345 """update self.config from a flag, which can be a dict or Config"""
350 """update self.config from a flag, which can be a dict or Config"""
346 if isinstance(cfg, (dict, Config)):
351 if isinstance(cfg, (dict, Config)):
347 # don't clobber whole config sections, update
352 # don't clobber whole config sections, update
348 # each section from config:
353 # each section from config:
349 for sec,c in cfg.iteritems():
354 for sec,c in cfg.iteritems():
350 self.config[sec].update(c)
355 self.config[sec].update(c)
351 else:
356 else:
352 raise ValueError("Invalid flag: '%s'"%raw)
357 raise ValueError("Invalid flag: '%s'"%raw)
353
358
354 # raw --identifier=value pattern
359 # raw --identifier=value pattern
355 # but *also* accept '-' as wordsep, for aliases
360 # but *also* accept '-' as wordsep, for aliases
356 # accepts: --foo=a
361 # accepts: --foo=a
357 # --Class.trait=value
362 # --Class.trait=value
358 # --alias-name=value
363 # --alias-name=value
359 # rejects: -foo=value
364 # rejects: -foo=value
360 # --foo
365 # --foo
361 # --Class.trait
366 # --Class.trait
362 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
367 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
363
368
364 # just flags, no assignments, with two *or one* leading '-'
369 # just flags, no assignments, with two *or one* leading '-'
365 # accepts: --foo
370 # accepts: --foo
366 # -foo-bar-again
371 # -foo-bar-again
367 # rejects: --anything=anything
372 # rejects: --anything=anything
368 # --two.word
373 # --two.word
369
374
370 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
375 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
371
376
372 class KeyValueConfigLoader(CommandLineConfigLoader):
377 class KeyValueConfigLoader(CommandLineConfigLoader):
373 """A config loader that loads key value pairs from the command line.
378 """A config loader that loads key value pairs from the command line.
374
379
375 This allows command line options to be gives in the following form::
380 This allows command line options to be gives in the following form::
376
381
377 ipython --profile="foo" --InteractiveShell.autocall=False
382 ipython --profile="foo" --InteractiveShell.autocall=False
378 """
383 """
379
384
380 def __init__(self, argv=None, aliases=None, flags=None):
385 def __init__(self, argv=None, aliases=None, flags=None):
381 """Create a key value pair config loader.
386 """Create a key value pair config loader.
382
387
383 Parameters
388 Parameters
384 ----------
389 ----------
385 argv : list
390 argv : list
386 A list that has the form of sys.argv[1:] which has unicode
391 A list that has the form of sys.argv[1:] which has unicode
387 elements of the form u"key=value". If this is None (default),
392 elements of the form u"key=value". If this is None (default),
388 then sys.argv[1:] will be used.
393 then sys.argv[1:] will be used.
389 aliases : dict
394 aliases : dict
390 A dict of aliases for configurable traits.
395 A dict of aliases for configurable traits.
391 Keys are the short aliases, Values are the resolved trait.
396 Keys are the short aliases, Values are the resolved trait.
392 Of the form: `{'alias' : 'Configurable.trait'}`
397 Of the form: `{'alias' : 'Configurable.trait'}`
393 flags : dict
398 flags : dict
394 A dict of flags, keyed by str name. Vaues can be Config objects,
399 A dict of flags, keyed by str name. Vaues can be Config objects,
395 dicts, or "key=value" strings. If Config or dict, when the flag
400 dicts, or "key=value" strings. If Config or dict, when the flag
396 is triggered, The flag is loaded as `self.config.update(m)`.
401 is triggered, The flag is loaded as `self.config.update(m)`.
397
402
398 Returns
403 Returns
399 -------
404 -------
400 config : Config
405 config : Config
401 The resulting Config object.
406 The resulting Config object.
402
407
403 Examples
408 Examples
404 --------
409 --------
405
410
406 >>> from IPython.config.loader import KeyValueConfigLoader
411 >>> from IPython.config.loader import KeyValueConfigLoader
407 >>> cl = KeyValueConfigLoader()
412 >>> cl = KeyValueConfigLoader()
408 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
413 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
409 {'A': {'name': 'brian'}, 'B': {'number': 0}}
414 {'A': {'name': 'brian'}, 'B': {'number': 0}}
410 """
415 """
411 self.clear()
416 self.clear()
412 if argv is None:
417 if argv is None:
413 argv = sys.argv[1:]
418 argv = sys.argv[1:]
414 self.argv = argv
419 self.argv = argv
415 self.aliases = aliases or {}
420 self.aliases = aliases or {}
416 self.flags = flags or {}
421 self.flags = flags or {}
417
422
418
423
419 def clear(self):
424 def clear(self):
420 super(KeyValueConfigLoader, self).clear()
425 super(KeyValueConfigLoader, self).clear()
421 self.extra_args = []
426 self.extra_args = []
422
427
423
428
424 def _decode_argv(self, argv, enc=None):
429 def _decode_argv(self, argv, enc=None):
425 """decode argv if bytes, using stin.encoding, falling back on default enc"""
430 """decode argv if bytes, using stin.encoding, falling back on default enc"""
426 uargv = []
431 uargv = []
427 if enc is None:
432 if enc is None:
428 enc = text.getdefaultencoding()
433 enc = text.getdefaultencoding()
429 for arg in argv:
434 for arg in argv:
430 if not isinstance(arg, unicode):
435 if not isinstance(arg, unicode):
431 # only decode if not already decoded
436 # only decode if not already decoded
432 arg = arg.decode(enc)
437 arg = arg.decode(enc)
433 uargv.append(arg)
438 uargv.append(arg)
434 return uargv
439 return uargv
435
440
436
441
437 def load_config(self, argv=None, aliases=None, flags=None):
442 def load_config(self, argv=None, aliases=None, flags=None):
438 """Parse the configuration and generate the Config object.
443 """Parse the configuration and generate the Config object.
439
444
440 After loading, any arguments that are not key-value or
445 After loading, any arguments that are not key-value or
441 flags will be stored in self.extra_args - a list of
446 flags will be stored in self.extra_args - a list of
442 unparsed command-line arguments. This is used for
447 unparsed command-line arguments. This is used for
443 arguments such as input files or subcommands.
448 arguments such as input files or subcommands.
444
449
445 Parameters
450 Parameters
446 ----------
451 ----------
447 argv : list, optional
452 argv : list, optional
448 A list that has the form of sys.argv[1:] which has unicode
453 A list that has the form of sys.argv[1:] which has unicode
449 elements of the form u"key=value". If this is None (default),
454 elements of the form u"key=value". If this is None (default),
450 then self.argv will be used.
455 then self.argv will be used.
451 aliases : dict
456 aliases : dict
452 A dict of aliases for configurable traits.
457 A dict of aliases for configurable traits.
453 Keys are the short aliases, Values are the resolved trait.
458 Keys are the short aliases, Values are the resolved trait.
454 Of the form: `{'alias' : 'Configurable.trait'}`
459 Of the form: `{'alias' : 'Configurable.trait'}`
455 flags : dict
460 flags : dict
456 A dict of flags, keyed by str name. Values can be Config objects
461 A dict of flags, keyed by str name. Values can be Config objects
457 or dicts. When the flag is triggered, The config is loaded as
462 or dicts. When the flag is triggered, The config is loaded as
458 `self.config.update(cfg)`.
463 `self.config.update(cfg)`.
459 """
464 """
460 from IPython.config.configurable import Configurable
465 from IPython.config.configurable import Configurable
461
466
462 self.clear()
467 self.clear()
463 if argv is None:
468 if argv is None:
464 argv = self.argv
469 argv = self.argv
465 if aliases is None:
470 if aliases is None:
466 aliases = self.aliases
471 aliases = self.aliases
467 if flags is None:
472 if flags is None:
468 flags = self.flags
473 flags = self.flags
469
474
470 # ensure argv is a list of unicode strings:
475 # ensure argv is a list of unicode strings:
471 uargv = self._decode_argv(argv)
476 uargv = self._decode_argv(argv)
472 for idx,raw in enumerate(uargv):
477 for idx,raw in enumerate(uargv):
473 # strip leading '-'
478 # strip leading '-'
474 item = raw.lstrip('-')
479 item = raw.lstrip('-')
475
480
476 if raw == '--':
481 if raw == '--':
477 # don't parse arguments after '--'
482 # don't parse arguments after '--'
478 # this is useful for relaying arguments to scripts, e.g.
483 # this is useful for relaying arguments to scripts, e.g.
479 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
484 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
480 self.extra_args.extend(uargv[idx+1:])
485 self.extra_args.extend(uargv[idx+1:])
481 break
486 break
482
487
483 if kv_pattern.match(raw):
488 if kv_pattern.match(raw):
484 lhs,rhs = item.split('=',1)
489 lhs,rhs = item.split('=',1)
485 # Substitute longnames for aliases.
490 # Substitute longnames for aliases.
486 if lhs in aliases:
491 if lhs in aliases:
487 lhs = aliases[lhs]
492 lhs = aliases[lhs]
488 if '.' not in lhs:
493 if '.' not in lhs:
489 # probably a mistyped alias, but not technically illegal
494 # probably a mistyped alias, but not technically illegal
490 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
495 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
491 self._exec_config_str(lhs, rhs)
496 self._exec_config_str(lhs, rhs)
492
497
493 elif flag_pattern.match(raw):
498 elif flag_pattern.match(raw):
494 if item in flags:
499 if item in flags:
495 cfg,help = flags[item]
500 cfg,help = flags[item]
496 self._load_flag(cfg)
501 self._load_flag(cfg)
497 else:
502 else:
498 raise ArgumentError("Unrecognized flag: '%s'"%raw)
503 raise ArgumentError("Unrecognized flag: '%s'"%raw)
499 elif raw.startswith('-'):
504 elif raw.startswith('-'):
500 kv = '--'+item
505 kv = '--'+item
501 if kv_pattern.match(kv):
506 if kv_pattern.match(kv):
502 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
507 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
503 else:
508 else:
504 raise ArgumentError("Invalid argument: '%s'"%raw)
509 raise ArgumentError("Invalid argument: '%s'"%raw)
505 else:
510 else:
506 # keep all args that aren't valid in a list,
511 # keep all args that aren't valid in a list,
507 # in case our parent knows what to do with them.
512 # in case our parent knows what to do with them.
508 self.extra_args.append(item)
513 self.extra_args.append(item)
509 return self.config
514 return self.config
510
515
511 class ArgParseConfigLoader(CommandLineConfigLoader):
516 class ArgParseConfigLoader(CommandLineConfigLoader):
512 """A loader that uses the argparse module to load from the command line."""
517 """A loader that uses the argparse module to load from the command line."""
513
518
514 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
519 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
515 """Create a config loader for use with argparse.
520 """Create a config loader for use with argparse.
516
521
517 Parameters
522 Parameters
518 ----------
523 ----------
519
524
520 argv : optional, list
525 argv : optional, list
521 If given, used to read command-line arguments from, otherwise
526 If given, used to read command-line arguments from, otherwise
522 sys.argv[1:] is used.
527 sys.argv[1:] is used.
523
528
524 parser_args : tuple
529 parser_args : tuple
525 A tuple of positional arguments that will be passed to the
530 A tuple of positional arguments that will be passed to the
526 constructor of :class:`argparse.ArgumentParser`.
531 constructor of :class:`argparse.ArgumentParser`.
527
532
528 parser_kw : dict
533 parser_kw : dict
529 A tuple of keyword arguments that will be passed to the
534 A tuple of keyword arguments that will be passed to the
530 constructor of :class:`argparse.ArgumentParser`.
535 constructor of :class:`argparse.ArgumentParser`.
531
536
532 Returns
537 Returns
533 -------
538 -------
534 config : Config
539 config : Config
535 The resulting Config object.
540 The resulting Config object.
536 """
541 """
537 super(CommandLineConfigLoader, self).__init__()
542 super(CommandLineConfigLoader, self).__init__()
538 self.clear()
543 self.clear()
539 if argv is None:
544 if argv is None:
540 argv = sys.argv[1:]
545 argv = sys.argv[1:]
541 self.argv = argv
546 self.argv = argv
542 self.aliases = aliases or {}
547 self.aliases = aliases or {}
543 self.flags = flags or {}
548 self.flags = flags or {}
544
549
545 self.parser_args = parser_args
550 self.parser_args = parser_args
546 self.version = parser_kw.pop("version", None)
551 self.version = parser_kw.pop("version", None)
547 kwargs = dict(argument_default=argparse.SUPPRESS)
552 kwargs = dict(argument_default=argparse.SUPPRESS)
548 kwargs.update(parser_kw)
553 kwargs.update(parser_kw)
549 self.parser_kw = kwargs
554 self.parser_kw = kwargs
550
555
551 def load_config(self, argv=None, aliases=None, flags=None):
556 def load_config(self, argv=None, aliases=None, flags=None):
552 """Parse command line arguments and return as a Config object.
557 """Parse command line arguments and return as a Config object.
553
558
554 Parameters
559 Parameters
555 ----------
560 ----------
556
561
557 args : optional, list
562 args : optional, list
558 If given, a list with the structure of sys.argv[1:] to parse
563 If given, a list with the structure of sys.argv[1:] to parse
559 arguments from. If not given, the instance's self.argv attribute
564 arguments from. If not given, the instance's self.argv attribute
560 (given at construction time) is used."""
565 (given at construction time) is used."""
561 self.clear()
566 self.clear()
562 if argv is None:
567 if argv is None:
563 argv = self.argv
568 argv = self.argv
564 if aliases is None:
569 if aliases is None:
565 aliases = self.aliases
570 aliases = self.aliases
566 if flags is None:
571 if flags is None:
567 flags = self.flags
572 flags = self.flags
568 self._create_parser(aliases, flags)
573 self._create_parser(aliases, flags)
569 self._parse_args(argv)
574 self._parse_args(argv)
570 self._convert_to_config()
575 self._convert_to_config()
571 return self.config
576 return self.config
572
577
573 def get_extra_args(self):
578 def get_extra_args(self):
574 if hasattr(self, 'extra_args'):
579 if hasattr(self, 'extra_args'):
575 return self.extra_args
580 return self.extra_args
576 else:
581 else:
577 return []
582 return []
578
583
579 def _create_parser(self, aliases=None, flags=None):
584 def _create_parser(self, aliases=None, flags=None):
580 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
585 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
581 self._add_arguments(aliases, flags)
586 self._add_arguments(aliases, flags)
582
587
583 def _add_arguments(self, aliases=None, flags=None):
588 def _add_arguments(self, aliases=None, flags=None):
584 raise NotImplementedError("subclasses must implement _add_arguments")
589 raise NotImplementedError("subclasses must implement _add_arguments")
585
590
586 def _parse_args(self, args):
591 def _parse_args(self, args):
587 """self.parser->self.parsed_data"""
592 """self.parser->self.parsed_data"""
588 # decode sys.argv to support unicode command-line options
593 # decode sys.argv to support unicode command-line options
589 enc = text.getdefaultencoding()
594 enc = text.getdefaultencoding()
590 uargs = [py3compat.cast_unicode(a, enc) for a in args]
595 uargs = [py3compat.cast_unicode(a, enc) for a in args]
591 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
596 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
592
597
593 def _convert_to_config(self):
598 def _convert_to_config(self):
594 """self.parsed_data->self.config"""
599 """self.parsed_data->self.config"""
595 for k, v in vars(self.parsed_data).iteritems():
600 for k, v in vars(self.parsed_data).iteritems():
596 exec "self.config.%s = v"%k in locals(), globals()
601 exec "self.config.%s = v"%k in locals(), globals()
597
602
598 class KVArgParseConfigLoader(ArgParseConfigLoader):
603 class KVArgParseConfigLoader(ArgParseConfigLoader):
599 """A config loader that loads aliases and flags with argparse,
604 """A config loader that loads aliases and flags with argparse,
600 but will use KVLoader for the rest. This allows better parsing
605 but will use KVLoader for the rest. This allows better parsing
601 of common args, such as `ipython -c 'print 5'`, but still gets
606 of common args, such as `ipython -c 'print 5'`, but still gets
602 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
607 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
603
608
604 def _convert_to_config(self):
609 def _convert_to_config(self):
605 """self.parsed_data->self.config"""
610 """self.parsed_data->self.config"""
606 for k, v in vars(self.parsed_data).iteritems():
611 for k, v in vars(self.parsed_data).iteritems():
607 self._exec_config_str(k, v)
612 self._exec_config_str(k, v)
608
613
609 def _add_arguments(self, aliases=None, flags=None):
614 def _add_arguments(self, aliases=None, flags=None):
610 self.alias_flags = {}
615 self.alias_flags = {}
611 # print aliases, flags
616 # print aliases, flags
612 if aliases is None:
617 if aliases is None:
613 aliases = self.aliases
618 aliases = self.aliases
614 if flags is None:
619 if flags is None:
615 flags = self.flags
620 flags = self.flags
616 paa = self.parser.add_argument
621 paa = self.parser.add_argument
617 for key,value in aliases.iteritems():
622 for key,value in aliases.iteritems():
618 if key in flags:
623 if key in flags:
619 # flags
624 # flags
620 nargs = '?'
625 nargs = '?'
621 else:
626 else:
622 nargs = None
627 nargs = None
623 if len(key) is 1:
628 if len(key) is 1:
624 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
629 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
625 else:
630 else:
626 paa('--'+key, type=unicode, dest=value, nargs=nargs)
631 paa('--'+key, type=unicode, dest=value, nargs=nargs)
627 for key, (value, help) in flags.iteritems():
632 for key, (value, help) in flags.iteritems():
628 if key in self.aliases:
633 if key in self.aliases:
629 #
634 #
630 self.alias_flags[self.aliases[key]] = value
635 self.alias_flags[self.aliases[key]] = value
631 continue
636 continue
632 if len(key) is 1:
637 if len(key) is 1:
633 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
638 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
634 else:
639 else:
635 paa('--'+key, action='append_const', dest='_flags', const=value)
640 paa('--'+key, action='append_const', dest='_flags', const=value)
636
641
637 def _convert_to_config(self):
642 def _convert_to_config(self):
638 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
643 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
639 # remove subconfigs list from namespace before transforming the Namespace
644 # remove subconfigs list from namespace before transforming the Namespace
640 if '_flags' in self.parsed_data:
645 if '_flags' in self.parsed_data:
641 subcs = self.parsed_data._flags
646 subcs = self.parsed_data._flags
642 del self.parsed_data._flags
647 del self.parsed_data._flags
643 else:
648 else:
644 subcs = []
649 subcs = []
645
650
646 for k, v in vars(self.parsed_data).iteritems():
651 for k, v in vars(self.parsed_data).iteritems():
647 if v is None:
652 if v is None:
648 # it was a flag that shares the name of an alias
653 # it was a flag that shares the name of an alias
649 subcs.append(self.alias_flags[k])
654 subcs.append(self.alias_flags[k])
650 else:
655 else:
651 # eval the KV assignment
656 # eval the KV assignment
652 self._exec_config_str(k, v)
657 self._exec_config_str(k, v)
653
658
654 for subc in subcs:
659 for subc in subcs:
655 self._load_flag(subc)
660 self._load_flag(subc)
656
661
657 if self.extra_args:
662 if self.extra_args:
658 sub_parser = KeyValueConfigLoader()
663 sub_parser = KeyValueConfigLoader()
659 sub_parser.load_config(self.extra_args)
664 sub_parser.load_config(self.extra_args)
660 self.config._merge(sub_parser.config)
665 self.config._merge(sub_parser.config)
661 self.extra_args = sub_parser.extra_args
666 self.extra_args = sub_parser.extra_args
General Comments 0
You need to be logged in to leave comments. Login now