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