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