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