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