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