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