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