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