##// END OF EJS Templates
remove unnecessary builtin import
MinRK -
Show More
@@ -1,811 +1,811 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 builtin_mod, 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 # we always have Sections
223 if _is_section_key(key):
223 if _is_section_key(key):
224 return True
224 return True
225 else:
225 else:
226 return super(Config, self).__contains__(key)
226 return super(Config, self).__contains__(key)
227 # .has_key is deprecated for dictionaries.
227 # .has_key is deprecated for dictionaries.
228 has_key = __contains__
228 has_key = __contains__
229
229
230 def _has_section(self, key):
230 def _has_section(self, key):
231 if _is_section_key(key):
231 if _is_section_key(key):
232 if super(Config, self).__contains__(key):
232 if super(Config, self).__contains__(key):
233 return True
233 return True
234 return False
234 return False
235
235
236 def copy(self):
236 def copy(self):
237 return type(self)(dict.copy(self))
237 return type(self)(dict.copy(self))
238
238
239 def __copy__(self):
239 def __copy__(self):
240 return self.copy()
240 return self.copy()
241
241
242 def __deepcopy__(self, memo):
242 def __deepcopy__(self, memo):
243 import copy
243 import copy
244 return type(self)(copy.deepcopy(list(self.items())))
244 return type(self)(copy.deepcopy(list(self.items())))
245
245
246 def __getitem__(self, key):
246 def __getitem__(self, key):
247 if _is_section_key(key):
247 if _is_section_key(key):
248 try:
248 try:
249 return dict.__getitem__(self, key)
249 return dict.__getitem__(self, key)
250 except KeyError:
250 except KeyError:
251 c = Config()
251 c = Config()
252 dict.__setitem__(self, key, c)
252 dict.__setitem__(self, key, c)
253 return c
253 return c
254 else:
254 else:
255 try:
255 try:
256 return dict.__getitem__(self, key)
256 return dict.__getitem__(self, key)
257 except KeyError:
257 except KeyError:
258 # undefined, create lazy value, used for container methods
258 # undefined, create lazy value, used for container methods
259 v = LazyConfigValue()
259 v = LazyConfigValue()
260 dict.__setitem__(self, key, v)
260 dict.__setitem__(self, key, v)
261 return v
261 return v
262
262
263 def __setitem__(self, key, value):
263 def __setitem__(self, key, value):
264 if _is_section_key(key):
264 if _is_section_key(key):
265 if not isinstance(value, Config):
265 if not isinstance(value, Config):
266 raise ValueError('values whose keys begin with an uppercase '
266 raise ValueError('values whose keys begin with an uppercase '
267 'char must be Config instances: %r, %r' % (key, value))
267 'char must be Config instances: %r, %r' % (key, value))
268 dict.__setitem__(self, key, value)
268 dict.__setitem__(self, key, value)
269
269
270 def __getattr__(self, key):
270 def __getattr__(self, key):
271 if key.startswith('__'):
271 if key.startswith('__'):
272 return dict.__getattr__(self, key)
272 return dict.__getattr__(self, key)
273 try:
273 try:
274 return self.__getitem__(key)
274 return self.__getitem__(key)
275 except KeyError as e:
275 except KeyError as e:
276 raise AttributeError(e)
276 raise AttributeError(e)
277
277
278 def __setattr__(self, key, value):
278 def __setattr__(self, key, value):
279 if key.startswith('__'):
279 if key.startswith('__'):
280 return dict.__setattr__(self, key, value)
280 return dict.__setattr__(self, key, value)
281 try:
281 try:
282 self.__setitem__(key, value)
282 self.__setitem__(key, value)
283 except KeyError as e:
283 except KeyError as e:
284 raise AttributeError(e)
284 raise AttributeError(e)
285
285
286 def __delattr__(self, key):
286 def __delattr__(self, key):
287 if key.startswith('__'):
287 if key.startswith('__'):
288 return dict.__delattr__(self, key)
288 return dict.__delattr__(self, key)
289 try:
289 try:
290 dict.__delitem__(self, key)
290 dict.__delitem__(self, key)
291 except KeyError as e:
291 except KeyError as e:
292 raise AttributeError(e)
292 raise AttributeError(e)
293
293
294
294
295 #-----------------------------------------------------------------------------
295 #-----------------------------------------------------------------------------
296 # Config loading classes
296 # Config loading classes
297 #-----------------------------------------------------------------------------
297 #-----------------------------------------------------------------------------
298
298
299
299
300 class ConfigLoader(object):
300 class ConfigLoader(object):
301 """A object for loading configurations from just about anywhere.
301 """A object for loading configurations from just about anywhere.
302
302
303 The resulting configuration is packaged as a :class:`Struct`.
303 The resulting configuration is packaged as a :class:`Struct`.
304
304
305 Notes
305 Notes
306 -----
306 -----
307 A :class:`ConfigLoader` does one thing: load a config from a source
307 A :class:`ConfigLoader` does one thing: load a config from a source
308 (file, command line arguments) and returns the data as a :class:`Struct`.
308 (file, command line arguments) and returns the data as a :class:`Struct`.
309 There are lots of things that :class:`ConfigLoader` does not do. It does
309 There are lots of things that :class:`ConfigLoader` does not do. It does
310 not implement complex logic for finding config files. It does not handle
310 not implement complex logic for finding config files. It does not handle
311 default values or merge multiple configs. These things need to be
311 default values or merge multiple configs. These things need to be
312 handled elsewhere.
312 handled elsewhere.
313 """
313 """
314
314
315 def __init__(self):
315 def __init__(self):
316 """A base class for config loaders.
316 """A base class for config loaders.
317
317
318 Examples
318 Examples
319 --------
319 --------
320
320
321 >>> cl = ConfigLoader()
321 >>> cl = ConfigLoader()
322 >>> config = cl.load_config()
322 >>> config = cl.load_config()
323 >>> config
323 >>> config
324 {}
324 {}
325 """
325 """
326 self.clear()
326 self.clear()
327
327
328 def clear(self):
328 def clear(self):
329 self.config = Config()
329 self.config = Config()
330
330
331 def load_config(self):
331 def load_config(self):
332 """Load a config from somewhere, return a :class:`Config` instance.
332 """Load a config from somewhere, return a :class:`Config` instance.
333
333
334 Usually, this will cause self.config to be set and then returned.
334 Usually, this will cause self.config to be set and then returned.
335 However, in most cases, :meth:`ConfigLoader.clear` should be called
335 However, in most cases, :meth:`ConfigLoader.clear` should be called
336 to erase any previous state.
336 to erase any previous state.
337 """
337 """
338 self.clear()
338 self.clear()
339 return self.config
339 return self.config
340
340
341
341
342 class FileConfigLoader(ConfigLoader):
342 class FileConfigLoader(ConfigLoader):
343 """A base class for file based configurations.
343 """A base class for file based configurations.
344
344
345 As we add more file based config loaders, the common logic should go
345 As we add more file based config loaders, the common logic should go
346 here.
346 here.
347 """
347 """
348 pass
348 pass
349
349
350
350
351 class PyFileConfigLoader(FileConfigLoader):
351 class PyFileConfigLoader(FileConfigLoader):
352 """A config loader for pure python files.
352 """A config loader for pure python files.
353
353
354 This calls execfile on a plain python file and looks for attributes
354 This calls execfile on a plain python file and looks for attributes
355 that are all caps. These attribute are added to the config Struct.
355 that are all caps. These attribute are added to the config Struct.
356 """
356 """
357
357
358 def __init__(self, filename, path=None):
358 def __init__(self, filename, path=None):
359 """Build a config loader for a filename and path.
359 """Build a config loader for a filename and path.
360
360
361 Parameters
361 Parameters
362 ----------
362 ----------
363 filename : str
363 filename : str
364 The file name of the config file.
364 The file name of the config file.
365 path : str, list, tuple
365 path : str, list, tuple
366 The path to search for the config file on, or a sequence of
366 The path to search for the config file on, or a sequence of
367 paths to try in order.
367 paths to try in order.
368 """
368 """
369 super(PyFileConfigLoader, self).__init__()
369 super(PyFileConfigLoader, self).__init__()
370 self.filename = filename
370 self.filename = filename
371 self.path = path
371 self.path = path
372 self.full_filename = ''
372 self.full_filename = ''
373 self.data = None
373 self.data = None
374
374
375 def load_config(self):
375 def load_config(self):
376 """Load the config from a file and return it as a Struct."""
376 """Load the config from a file and return it as a Struct."""
377 self.clear()
377 self.clear()
378 try:
378 try:
379 self._find_file()
379 self._find_file()
380 except IOError as e:
380 except IOError as e:
381 raise ConfigFileNotFound(str(e))
381 raise ConfigFileNotFound(str(e))
382 self._read_file_as_dict()
382 self._read_file_as_dict()
383 self._convert_to_config()
383 self._convert_to_config()
384 return self.config
384 return self.config
385
385
386 def _find_file(self):
386 def _find_file(self):
387 """Try to find the file by searching the paths."""
387 """Try to find the file by searching the paths."""
388 self.full_filename = filefind(self.filename, self.path)
388 self.full_filename = filefind(self.filename, self.path)
389
389
390 def _read_file_as_dict(self):
390 def _read_file_as_dict(self):
391 """Load the config file into self.config, with recursive loading."""
391 """Load the config file into self.config, with recursive loading."""
392 # This closure is made available in the namespace that is used
392 # This closure is made available in the namespace that is used
393 # to exec the config file. It allows users to call
393 # to exec the config file. It allows users to call
394 # load_subconfig('myconfig.py') to load config files recursively.
394 # load_subconfig('myconfig.py') to load config files recursively.
395 # It needs to be a closure because it has references to self.path
395 # It needs to be a closure because it has references to self.path
396 # and self.config. The sub-config is loaded with the same path
396 # and self.config. The sub-config is loaded with the same path
397 # as the parent, but it uses an empty config which is then merged
397 # as the parent, but it uses an empty config which is then merged
398 # with the parents.
398 # with the parents.
399
399
400 # If a profile is specified, the config file will be loaded
400 # If a profile is specified, the config file will be loaded
401 # from that profile
401 # from that profile
402
402
403 def load_subconfig(fname, profile=None):
403 def load_subconfig(fname, profile=None):
404 # import here to prevent circular imports
404 # import here to prevent circular imports
405 from IPython.core.profiledir import ProfileDir, ProfileDirError
405 from IPython.core.profiledir import ProfileDir, ProfileDirError
406 if profile is not None:
406 if profile is not None:
407 try:
407 try:
408 profile_dir = ProfileDir.find_profile_dir_by_name(
408 profile_dir = ProfileDir.find_profile_dir_by_name(
409 get_ipython_dir(),
409 get_ipython_dir(),
410 profile,
410 profile,
411 )
411 )
412 except ProfileDirError:
412 except ProfileDirError:
413 return
413 return
414 path = profile_dir.location
414 path = profile_dir.location
415 else:
415 else:
416 path = self.path
416 path = self.path
417 loader = PyFileConfigLoader(fname, path)
417 loader = PyFileConfigLoader(fname, path)
418 try:
418 try:
419 sub_config = loader.load_config()
419 sub_config = loader.load_config()
420 except ConfigFileNotFound:
420 except ConfigFileNotFound:
421 # Pass silently if the sub config is not there. This happens
421 # Pass silently if the sub config is not there. This happens
422 # when a user s using a profile, but not the default config.
422 # when a user s using a profile, but not the default config.
423 pass
423 pass
424 else:
424 else:
425 self.config.merge(sub_config)
425 self.config.merge(sub_config)
426
426
427 # Again, this needs to be a closure and should be used in config
427 # Again, this needs to be a closure and should be used in config
428 # files to get the config being loaded.
428 # files to get the config being loaded.
429 def get_config():
429 def get_config():
430 return self.config
430 return self.config
431
431
432 namespace = dict(
432 namespace = dict(
433 load_subconfig=load_subconfig,
433 load_subconfig=load_subconfig,
434 get_config=get_config,
434 get_config=get_config,
435 __file__=self.full_filename,
435 __file__=self.full_filename,
436 )
436 )
437 fs_encoding = sys.getfilesystemencoding() or 'ascii'
437 fs_encoding = sys.getfilesystemencoding() or 'ascii'
438 conf_filename = self.full_filename.encode(fs_encoding)
438 conf_filename = self.full_filename.encode(fs_encoding)
439 py3compat.execfile(conf_filename, namespace)
439 py3compat.execfile(conf_filename, namespace)
440
440
441 def _convert_to_config(self):
441 def _convert_to_config(self):
442 if self.data is None:
442 if self.data is None:
443 ConfigLoaderError('self.data does not exist')
443 ConfigLoaderError('self.data does not exist')
444
444
445
445
446 class CommandLineConfigLoader(ConfigLoader):
446 class CommandLineConfigLoader(ConfigLoader):
447 """A config loader for command line arguments.
447 """A config loader for command line arguments.
448
448
449 As we add more command line based loaders, the common logic should go
449 As we add more command line based loaders, the common logic should go
450 here.
450 here.
451 """
451 """
452
452
453 def _exec_config_str(self, lhs, rhs):
453 def _exec_config_str(self, lhs, rhs):
454 """execute self.config.<lhs> = <rhs>
454 """execute self.config.<lhs> = <rhs>
455
455
456 * expands ~ with expanduser
456 * expands ~ with expanduser
457 * tries to assign with raw eval, otherwise assigns with just the string,
457 * tries to assign with raw eval, otherwise assigns with just the string,
458 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
458 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
459 equivalent are `--C.a=4` and `--C.a='4'`.
459 equivalent are `--C.a=4` and `--C.a='4'`.
460 """
460 """
461 rhs = os.path.expanduser(rhs)
461 rhs = os.path.expanduser(rhs)
462 try:
462 try:
463 # Try to see if regular Python syntax will work. This
463 # Try to see if regular Python syntax will work. This
464 # won't handle strings as the quote marks are removed
464 # won't handle strings as the quote marks are removed
465 # by the system shell.
465 # by the system shell.
466 value = eval(rhs)
466 value = eval(rhs)
467 except (NameError, SyntaxError):
467 except (NameError, SyntaxError):
468 # This case happens if the rhs is a string.
468 # This case happens if the rhs is a string.
469 value = rhs
469 value = rhs
470
470
471 exec(u'self.config.%s = value' % lhs)
471 exec(u'self.config.%s = value' % lhs)
472
472
473 def _load_flag(self, cfg):
473 def _load_flag(self, cfg):
474 """update self.config from a flag, which can be a dict or Config"""
474 """update self.config from a flag, which can be a dict or Config"""
475 if isinstance(cfg, (dict, Config)):
475 if isinstance(cfg, (dict, Config)):
476 # don't clobber whole config sections, update
476 # don't clobber whole config sections, update
477 # each section from config:
477 # each section from config:
478 for sec,c in iteritems(cfg):
478 for sec,c in iteritems(cfg):
479 self.config[sec].update(c)
479 self.config[sec].update(c)
480 else:
480 else:
481 raise TypeError("Invalid flag: %r" % cfg)
481 raise TypeError("Invalid flag: %r" % cfg)
482
482
483 # raw --identifier=value pattern
483 # raw --identifier=value pattern
484 # but *also* accept '-' as wordsep, for aliases
484 # but *also* accept '-' as wordsep, for aliases
485 # accepts: --foo=a
485 # accepts: --foo=a
486 # --Class.trait=value
486 # --Class.trait=value
487 # --alias-name=value
487 # --alias-name=value
488 # rejects: -foo=value
488 # rejects: -foo=value
489 # --foo
489 # --foo
490 # --Class.trait
490 # --Class.trait
491 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
491 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
492
492
493 # just flags, no assignments, with two *or one* leading '-'
493 # just flags, no assignments, with two *or one* leading '-'
494 # accepts: --foo
494 # accepts: --foo
495 # -foo-bar-again
495 # -foo-bar-again
496 # rejects: --anything=anything
496 # rejects: --anything=anything
497 # --two.word
497 # --two.word
498
498
499 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
499 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
500
500
501 class KeyValueConfigLoader(CommandLineConfigLoader):
501 class KeyValueConfigLoader(CommandLineConfigLoader):
502 """A config loader that loads key value pairs from the command line.
502 """A config loader that loads key value pairs from the command line.
503
503
504 This allows command line options to be gives in the following form::
504 This allows command line options to be gives in the following form::
505
505
506 ipython --profile="foo" --InteractiveShell.autocall=False
506 ipython --profile="foo" --InteractiveShell.autocall=False
507 """
507 """
508
508
509 def __init__(self, argv=None, aliases=None, flags=None):
509 def __init__(self, argv=None, aliases=None, flags=None):
510 """Create a key value pair config loader.
510 """Create a key value pair config loader.
511
511
512 Parameters
512 Parameters
513 ----------
513 ----------
514 argv : list
514 argv : list
515 A list that has the form of sys.argv[1:] which has unicode
515 A list that has the form of sys.argv[1:] which has unicode
516 elements of the form u"key=value". If this is None (default),
516 elements of the form u"key=value". If this is None (default),
517 then sys.argv[1:] will be used.
517 then sys.argv[1:] will be used.
518 aliases : dict
518 aliases : dict
519 A dict of aliases for configurable traits.
519 A dict of aliases for configurable traits.
520 Keys are the short aliases, Values are the resolved trait.
520 Keys are the short aliases, Values are the resolved trait.
521 Of the form: `{'alias' : 'Configurable.trait'}`
521 Of the form: `{'alias' : 'Configurable.trait'}`
522 flags : dict
522 flags : dict
523 A dict of flags, keyed by str name. Vaues can be Config objects,
523 A dict of flags, keyed by str name. Vaues can be Config objects,
524 dicts, or "key=value" strings. If Config or dict, when the flag
524 dicts, or "key=value" strings. If Config or dict, when the flag
525 is triggered, The flag is loaded as `self.config.update(m)`.
525 is triggered, The flag is loaded as `self.config.update(m)`.
526
526
527 Returns
527 Returns
528 -------
528 -------
529 config : Config
529 config : Config
530 The resulting Config object.
530 The resulting Config object.
531
531
532 Examples
532 Examples
533 --------
533 --------
534
534
535 >>> from IPython.config.loader import KeyValueConfigLoader
535 >>> from IPython.config.loader import KeyValueConfigLoader
536 >>> cl = KeyValueConfigLoader()
536 >>> cl = KeyValueConfigLoader()
537 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
537 >>> d = cl.load_config(["--A.name='brian'","--B.number=0"])
538 >>> sorted(d.items())
538 >>> sorted(d.items())
539 [('A', {'name': 'brian'}), ('B', {'number': 0})]
539 [('A', {'name': 'brian'}), ('B', {'number': 0})]
540 """
540 """
541 self.clear()
541 self.clear()
542 if argv is None:
542 if argv is None:
543 argv = sys.argv[1:]
543 argv = sys.argv[1:]
544 self.argv = argv
544 self.argv = argv
545 self.aliases = aliases or {}
545 self.aliases = aliases or {}
546 self.flags = flags or {}
546 self.flags = flags or {}
547
547
548
548
549 def clear(self):
549 def clear(self):
550 super(KeyValueConfigLoader, self).clear()
550 super(KeyValueConfigLoader, self).clear()
551 self.extra_args = []
551 self.extra_args = []
552
552
553
553
554 def _decode_argv(self, argv, enc=None):
554 def _decode_argv(self, argv, enc=None):
555 """decode argv if bytes, using stin.encoding, falling back on default enc"""
555 """decode argv if bytes, using stin.encoding, falling back on default enc"""
556 uargv = []
556 uargv = []
557 if enc is None:
557 if enc is None:
558 enc = DEFAULT_ENCODING
558 enc = DEFAULT_ENCODING
559 for arg in argv:
559 for arg in argv:
560 if not isinstance(arg, unicode_type):
560 if not isinstance(arg, unicode_type):
561 # only decode if not already decoded
561 # only decode if not already decoded
562 arg = arg.decode(enc)
562 arg = arg.decode(enc)
563 uargv.append(arg)
563 uargv.append(arg)
564 return uargv
564 return uargv
565
565
566
566
567 def load_config(self, argv=None, aliases=None, flags=None):
567 def load_config(self, argv=None, aliases=None, flags=None):
568 """Parse the configuration and generate the Config object.
568 """Parse the configuration and generate the Config object.
569
569
570 After loading, any arguments that are not key-value or
570 After loading, any arguments that are not key-value or
571 flags will be stored in self.extra_args - a list of
571 flags will be stored in self.extra_args - a list of
572 unparsed command-line arguments. This is used for
572 unparsed command-line arguments. This is used for
573 arguments such as input files or subcommands.
573 arguments such as input files or subcommands.
574
574
575 Parameters
575 Parameters
576 ----------
576 ----------
577 argv : list, optional
577 argv : list, optional
578 A list that has the form of sys.argv[1:] which has unicode
578 A list that has the form of sys.argv[1:] which has unicode
579 elements of the form u"key=value". If this is None (default),
579 elements of the form u"key=value". If this is None (default),
580 then self.argv will be used.
580 then self.argv will be used.
581 aliases : dict
581 aliases : dict
582 A dict of aliases for configurable traits.
582 A dict of aliases for configurable traits.
583 Keys are the short aliases, Values are the resolved trait.
583 Keys are the short aliases, Values are the resolved trait.
584 Of the form: `{'alias' : 'Configurable.trait'}`
584 Of the form: `{'alias' : 'Configurable.trait'}`
585 flags : dict
585 flags : dict
586 A dict of flags, keyed by str name. Values can be Config objects
586 A dict of flags, keyed by str name. Values can be Config objects
587 or dicts. When the flag is triggered, The config is loaded as
587 or dicts. When the flag is triggered, The config is loaded as
588 `self.config.update(cfg)`.
588 `self.config.update(cfg)`.
589 """
589 """
590 self.clear()
590 self.clear()
591 if argv is None:
591 if argv is None:
592 argv = self.argv
592 argv = self.argv
593 if aliases is None:
593 if aliases is None:
594 aliases = self.aliases
594 aliases = self.aliases
595 if flags is None:
595 if flags is None:
596 flags = self.flags
596 flags = self.flags
597
597
598 # ensure argv is a list of unicode strings:
598 # ensure argv is a list of unicode strings:
599 uargv = self._decode_argv(argv)
599 uargv = self._decode_argv(argv)
600 for idx,raw in enumerate(uargv):
600 for idx,raw in enumerate(uargv):
601 # strip leading '-'
601 # strip leading '-'
602 item = raw.lstrip('-')
602 item = raw.lstrip('-')
603
603
604 if raw == '--':
604 if raw == '--':
605 # don't parse arguments after '--'
605 # don't parse arguments after '--'
606 # this is useful for relaying arguments to scripts, e.g.
606 # this is useful for relaying arguments to scripts, e.g.
607 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
607 # ipython -i foo.py --matplotlib=qt -- args after '--' go-to-foo.py
608 self.extra_args.extend(uargv[idx+1:])
608 self.extra_args.extend(uargv[idx+1:])
609 break
609 break
610
610
611 if kv_pattern.match(raw):
611 if kv_pattern.match(raw):
612 lhs,rhs = item.split('=',1)
612 lhs,rhs = item.split('=',1)
613 # Substitute longnames for aliases.
613 # Substitute longnames for aliases.
614 if lhs in aliases:
614 if lhs in aliases:
615 lhs = aliases[lhs]
615 lhs = aliases[lhs]
616 if '.' not in lhs:
616 if '.' not in lhs:
617 # probably a mistyped alias, but not technically illegal
617 # probably a mistyped alias, but not technically illegal
618 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
618 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
619 try:
619 try:
620 self._exec_config_str(lhs, rhs)
620 self._exec_config_str(lhs, rhs)
621 except Exception:
621 except Exception:
622 raise ArgumentError("Invalid argument: '%s'" % raw)
622 raise ArgumentError("Invalid argument: '%s'" % raw)
623
623
624 elif flag_pattern.match(raw):
624 elif flag_pattern.match(raw):
625 if item in flags:
625 if item in flags:
626 cfg,help = flags[item]
626 cfg,help = flags[item]
627 self._load_flag(cfg)
627 self._load_flag(cfg)
628 else:
628 else:
629 raise ArgumentError("Unrecognized flag: '%s'"%raw)
629 raise ArgumentError("Unrecognized flag: '%s'"%raw)
630 elif raw.startswith('-'):
630 elif raw.startswith('-'):
631 kv = '--'+item
631 kv = '--'+item
632 if kv_pattern.match(kv):
632 if kv_pattern.match(kv):
633 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
633 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
634 else:
634 else:
635 raise ArgumentError("Invalid argument: '%s'"%raw)
635 raise ArgumentError("Invalid argument: '%s'"%raw)
636 else:
636 else:
637 # keep all args that aren't valid in a list,
637 # keep all args that aren't valid in a list,
638 # in case our parent knows what to do with them.
638 # in case our parent knows what to do with them.
639 self.extra_args.append(item)
639 self.extra_args.append(item)
640 return self.config
640 return self.config
641
641
642 class ArgParseConfigLoader(CommandLineConfigLoader):
642 class ArgParseConfigLoader(CommandLineConfigLoader):
643 """A loader that uses the argparse module to load from the command line."""
643 """A loader that uses the argparse module to load from the command line."""
644
644
645 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
645 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
646 """Create a config loader for use with argparse.
646 """Create a config loader for use with argparse.
647
647
648 Parameters
648 Parameters
649 ----------
649 ----------
650
650
651 argv : optional, list
651 argv : optional, list
652 If given, used to read command-line arguments from, otherwise
652 If given, used to read command-line arguments from, otherwise
653 sys.argv[1:] is used.
653 sys.argv[1:] is used.
654
654
655 parser_args : tuple
655 parser_args : tuple
656 A tuple of positional arguments that will be passed to the
656 A tuple of positional arguments that will be passed to the
657 constructor of :class:`argparse.ArgumentParser`.
657 constructor of :class:`argparse.ArgumentParser`.
658
658
659 parser_kw : dict
659 parser_kw : dict
660 A tuple of keyword arguments that will be passed to the
660 A tuple of keyword arguments that will be passed to the
661 constructor of :class:`argparse.ArgumentParser`.
661 constructor of :class:`argparse.ArgumentParser`.
662
662
663 Returns
663 Returns
664 -------
664 -------
665 config : Config
665 config : Config
666 The resulting Config object.
666 The resulting Config object.
667 """
667 """
668 super(CommandLineConfigLoader, self).__init__()
668 super(CommandLineConfigLoader, self).__init__()
669 self.clear()
669 self.clear()
670 if argv is None:
670 if argv is None:
671 argv = sys.argv[1:]
671 argv = sys.argv[1:]
672 self.argv = argv
672 self.argv = argv
673 self.aliases = aliases or {}
673 self.aliases = aliases or {}
674 self.flags = flags or {}
674 self.flags = flags or {}
675
675
676 self.parser_args = parser_args
676 self.parser_args = parser_args
677 self.version = parser_kw.pop("version", None)
677 self.version = parser_kw.pop("version", None)
678 kwargs = dict(argument_default=argparse.SUPPRESS)
678 kwargs = dict(argument_default=argparse.SUPPRESS)
679 kwargs.update(parser_kw)
679 kwargs.update(parser_kw)
680 self.parser_kw = kwargs
680 self.parser_kw = kwargs
681
681
682 def load_config(self, argv=None, aliases=None, flags=None):
682 def load_config(self, argv=None, aliases=None, flags=None):
683 """Parse command line arguments and return as a Config object.
683 """Parse command line arguments and return as a Config object.
684
684
685 Parameters
685 Parameters
686 ----------
686 ----------
687
687
688 args : optional, list
688 args : optional, list
689 If given, a list with the structure of sys.argv[1:] to parse
689 If given, a list with the structure of sys.argv[1:] to parse
690 arguments from. If not given, the instance's self.argv attribute
690 arguments from. If not given, the instance's self.argv attribute
691 (given at construction time) is used."""
691 (given at construction time) is used."""
692 self.clear()
692 self.clear()
693 if argv is None:
693 if argv is None:
694 argv = self.argv
694 argv = self.argv
695 if aliases is None:
695 if aliases is None:
696 aliases = self.aliases
696 aliases = self.aliases
697 if flags is None:
697 if flags is None:
698 flags = self.flags
698 flags = self.flags
699 self._create_parser(aliases, flags)
699 self._create_parser(aliases, flags)
700 self._parse_args(argv)
700 self._parse_args(argv)
701 self._convert_to_config()
701 self._convert_to_config()
702 return self.config
702 return self.config
703
703
704 def get_extra_args(self):
704 def get_extra_args(self):
705 if hasattr(self, 'extra_args'):
705 if hasattr(self, 'extra_args'):
706 return self.extra_args
706 return self.extra_args
707 else:
707 else:
708 return []
708 return []
709
709
710 def _create_parser(self, aliases=None, flags=None):
710 def _create_parser(self, aliases=None, flags=None):
711 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
711 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
712 self._add_arguments(aliases, flags)
712 self._add_arguments(aliases, flags)
713
713
714 def _add_arguments(self, aliases=None, flags=None):
714 def _add_arguments(self, aliases=None, flags=None):
715 raise NotImplementedError("subclasses must implement _add_arguments")
715 raise NotImplementedError("subclasses must implement _add_arguments")
716
716
717 def _parse_args(self, args):
717 def _parse_args(self, args):
718 """self.parser->self.parsed_data"""
718 """self.parser->self.parsed_data"""
719 # decode sys.argv to support unicode command-line options
719 # decode sys.argv to support unicode command-line options
720 enc = DEFAULT_ENCODING
720 enc = DEFAULT_ENCODING
721 uargs = [py3compat.cast_unicode(a, enc) for a in args]
721 uargs = [py3compat.cast_unicode(a, enc) for a in args]
722 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
722 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
723
723
724 def _convert_to_config(self):
724 def _convert_to_config(self):
725 """self.parsed_data->self.config"""
725 """self.parsed_data->self.config"""
726 for k, v in iteritems(vars(self.parsed_data)):
726 for k, v in iteritems(vars(self.parsed_data)):
727 exec("self.config.%s = v"%k, locals(), globals())
727 exec("self.config.%s = v"%k, locals(), globals())
728
728
729 class KVArgParseConfigLoader(ArgParseConfigLoader):
729 class KVArgParseConfigLoader(ArgParseConfigLoader):
730 """A config loader that loads aliases and flags with argparse,
730 """A config loader that loads aliases and flags with argparse,
731 but will use KVLoader for the rest. This allows better parsing
731 but will use KVLoader for the rest. This allows better parsing
732 of common args, such as `ipython -c 'print 5'`, but still gets
732 of common args, such as `ipython -c 'print 5'`, but still gets
733 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
733 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
734
734
735 def _add_arguments(self, aliases=None, flags=None):
735 def _add_arguments(self, aliases=None, flags=None):
736 self.alias_flags = {}
736 self.alias_flags = {}
737 # print aliases, flags
737 # print aliases, flags
738 if aliases is None:
738 if aliases is None:
739 aliases = self.aliases
739 aliases = self.aliases
740 if flags is None:
740 if flags is None:
741 flags = self.flags
741 flags = self.flags
742 paa = self.parser.add_argument
742 paa = self.parser.add_argument
743 for key,value in iteritems(aliases):
743 for key,value in iteritems(aliases):
744 if key in flags:
744 if key in flags:
745 # flags
745 # flags
746 nargs = '?'
746 nargs = '?'
747 else:
747 else:
748 nargs = None
748 nargs = None
749 if len(key) is 1:
749 if len(key) is 1:
750 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
750 paa('-'+key, '--'+key, type=unicode_type, dest=value, nargs=nargs)
751 else:
751 else:
752 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
752 paa('--'+key, type=unicode_type, dest=value, nargs=nargs)
753 for key, (value, help) in iteritems(flags):
753 for key, (value, help) in iteritems(flags):
754 if key in self.aliases:
754 if key in self.aliases:
755 #
755 #
756 self.alias_flags[self.aliases[key]] = value
756 self.alias_flags[self.aliases[key]] = value
757 continue
757 continue
758 if len(key) is 1:
758 if len(key) is 1:
759 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
759 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
760 else:
760 else:
761 paa('--'+key, action='append_const', dest='_flags', const=value)
761 paa('--'+key, action='append_const', dest='_flags', const=value)
762
762
763 def _convert_to_config(self):
763 def _convert_to_config(self):
764 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
764 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
765 # remove subconfigs list from namespace before transforming the Namespace
765 # remove subconfigs list from namespace before transforming the Namespace
766 if '_flags' in self.parsed_data:
766 if '_flags' in self.parsed_data:
767 subcs = self.parsed_data._flags
767 subcs = self.parsed_data._flags
768 del self.parsed_data._flags
768 del self.parsed_data._flags
769 else:
769 else:
770 subcs = []
770 subcs = []
771
771
772 for k, v in iteritems(vars(self.parsed_data)):
772 for k, v in iteritems(vars(self.parsed_data)):
773 if v is None:
773 if v is None:
774 # it was a flag that shares the name of an alias
774 # it was a flag that shares the name of an alias
775 subcs.append(self.alias_flags[k])
775 subcs.append(self.alias_flags[k])
776 else:
776 else:
777 # eval the KV assignment
777 # eval the KV assignment
778 self._exec_config_str(k, v)
778 self._exec_config_str(k, v)
779
779
780 for subc in subcs:
780 for subc in subcs:
781 self._load_flag(subc)
781 self._load_flag(subc)
782
782
783 if self.extra_args:
783 if self.extra_args:
784 sub_parser = KeyValueConfigLoader()
784 sub_parser = KeyValueConfigLoader()
785 sub_parser.load_config(self.extra_args)
785 sub_parser.load_config(self.extra_args)
786 self.config.merge(sub_parser.config)
786 self.config.merge(sub_parser.config)
787 self.extra_args = sub_parser.extra_args
787 self.extra_args = sub_parser.extra_args
788
788
789
789
790 def load_pyconfig_files(config_files, path):
790 def load_pyconfig_files(config_files, path):
791 """Load multiple Python config files, merging each of them in turn.
791 """Load multiple Python config files, merging each of them in turn.
792
792
793 Parameters
793 Parameters
794 ==========
794 ==========
795 config_files : list of str
795 config_files : list of str
796 List of config files names to load and merge into the config.
796 List of config files names to load and merge into the config.
797 path : unicode
797 path : unicode
798 The full path to the location of the config files.
798 The full path to the location of the config files.
799 """
799 """
800 config = Config()
800 config = Config()
801 for cf in config_files:
801 for cf in config_files:
802 loader = PyFileConfigLoader(cf, path=path)
802 loader = PyFileConfigLoader(cf, path=path)
803 try:
803 try:
804 next_config = loader.load_config()
804 next_config = loader.load_config()
805 except ConfigFileNotFound:
805 except ConfigFileNotFound:
806 pass
806 pass
807 except:
807 except:
808 raise
808 raise
809 else:
809 else:
810 config.merge(next_config)
810 config.merge(next_config)
811 return config
811 return config
General Comments 0
You need to be logged in to leave comments. Login now