##// END OF EJS Templates
expand '~' with path.expanduser in KVLoader...
MinRK -
Show More
@@ -1,666 +1,675 b''
1 """A simple configuration system.
1 """A simple configuration system.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * Brian Granger
5 * Brian Granger
6 * Fernando Perez
6 * Fernando Perez
7 * Min RK
7 * Min RK
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import __builtin__ as builtin_mod
21 import __builtin__ as builtin_mod
22 import os
22 import re
23 import re
23 import sys
24 import sys
24
25
25 from IPython.external import argparse
26 from IPython.external import argparse
26 from IPython.utils.path import filefind, get_ipython_dir
27 from IPython.utils.path import filefind, get_ipython_dir
27 from IPython.utils import py3compat, text, warn
28 from IPython.utils import py3compat, text, warn
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Exceptions
31 # Exceptions
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32
33
33
34
34 class ConfigError(Exception):
35 class ConfigError(Exception):
35 pass
36 pass
36
37
37 class ConfigLoaderError(ConfigError):
38 class ConfigLoaderError(ConfigError):
38 pass
39 pass
39
40
40 class ConfigFileNotFound(ConfigError):
41 class ConfigFileNotFound(ConfigError):
41 pass
42 pass
42
43
43 class ArgumentError(ConfigLoaderError):
44 class ArgumentError(ConfigLoaderError):
44 pass
45 pass
45
46
46 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
47 # Argparse fix
48 # Argparse fix
48 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
49
50
50 # Unfortunately argparse by default prints help messages to stderr instead of
51 # Unfortunately argparse by default prints help messages to stderr instead of
51 # stdout. This makes it annoying to capture long help screens at the command
52 # stdout. This makes it annoying to capture long help screens at the command
52 # line, since one must know how to pipe stderr, which many users don't know how
53 # line, since one must know how to pipe stderr, which many users don't know how
53 # to do. So we override the print_help method with one that defaults to
54 # to do. So we override the print_help method with one that defaults to
54 # stdout and use our class instead.
55 # stdout and use our class instead.
55
56
56 class ArgumentParser(argparse.ArgumentParser):
57 class ArgumentParser(argparse.ArgumentParser):
57 """Simple argparse subclass that prints help to stdout by default."""
58 """Simple argparse subclass that prints help to stdout by default."""
58
59
59 def print_help(self, file=None):
60 def print_help(self, file=None):
60 if file is None:
61 if file is None:
61 file = sys.stdout
62 file = sys.stdout
62 return super(ArgumentParser, self).print_help(file)
63 return super(ArgumentParser, self).print_help(file)
63
64
64 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
65 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
65
66
66 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
67 # Config class for holding config information
68 # Config class for holding config information
68 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
69
70
70
71
71 class Config(dict):
72 class Config(dict):
72 """An attribute based dict that can do smart merges."""
73 """An attribute based dict that can do smart merges."""
73
74
74 def __init__(self, *args, **kwds):
75 def __init__(self, *args, **kwds):
75 dict.__init__(self, *args, **kwds)
76 dict.__init__(self, *args, **kwds)
76 # This sets self.__dict__ = self, but it has to be done this way
77 # This sets self.__dict__ = self, but it has to be done this way
77 # because we are also overriding __setattr__.
78 # because we are also overriding __setattr__.
78 dict.__setattr__(self, '__dict__', self)
79 dict.__setattr__(self, '__dict__', self)
79
80
80 def _merge(self, other):
81 def _merge(self, other):
81 to_update = {}
82 to_update = {}
82 for k, v in other.iteritems():
83 for k, v in other.iteritems():
83 if not self.has_key(k):
84 if not self.has_key(k):
84 to_update[k] = v
85 to_update[k] = v
85 else: # I have this key
86 else: # I have this key
86 if isinstance(v, Config):
87 if isinstance(v, Config):
87 # Recursively merge common sub Configs
88 # Recursively merge common sub Configs
88 self[k]._merge(v)
89 self[k]._merge(v)
89 else:
90 else:
90 # Plain updates for non-Configs
91 # Plain updates for non-Configs
91 to_update[k] = v
92 to_update[k] = v
92
93
93 self.update(to_update)
94 self.update(to_update)
94
95
95 def _is_section_key(self, key):
96 def _is_section_key(self, key):
96 if key[0].upper()==key[0] and not key.startswith('_'):
97 if key[0].upper()==key[0] and not key.startswith('_'):
97 return True
98 return True
98 else:
99 else:
99 return False
100 return False
100
101
101 def __contains__(self, key):
102 def __contains__(self, key):
102 if self._is_section_key(key):
103 if self._is_section_key(key):
103 return True
104 return True
104 else:
105 else:
105 return super(Config, self).__contains__(key)
106 return super(Config, self).__contains__(key)
106 # .has_key is deprecated for dictionaries.
107 # .has_key is deprecated for dictionaries.
107 has_key = __contains__
108 has_key = __contains__
108
109
109 def _has_section(self, key):
110 def _has_section(self, key):
110 if self._is_section_key(key):
111 if self._is_section_key(key):
111 if super(Config, self).__contains__(key):
112 if super(Config, self).__contains__(key):
112 return True
113 return True
113 return False
114 return False
114
115
115 def copy(self):
116 def copy(self):
116 return type(self)(dict.copy(self))
117 return type(self)(dict.copy(self))
117
118
118 def __copy__(self):
119 def __copy__(self):
119 return self.copy()
120 return self.copy()
120
121
121 def __deepcopy__(self, memo):
122 def __deepcopy__(self, memo):
122 import copy
123 import copy
123 return type(self)(copy.deepcopy(self.items()))
124 return type(self)(copy.deepcopy(self.items()))
124
125
125 def __getitem__(self, key):
126 def __getitem__(self, key):
126 # We cannot use directly self._is_section_key, because it triggers
127 # We cannot use directly self._is_section_key, because it triggers
127 # infinite recursion on top of PyPy. Instead, we manually fish the
128 # infinite recursion on top of PyPy. Instead, we manually fish the
128 # bound method.
129 # bound method.
129 is_section_key = self.__class__._is_section_key.__get__(self)
130 is_section_key = self.__class__._is_section_key.__get__(self)
130
131
131 # Because we use this for an exec namespace, we need to delegate
132 # Because we use this for an exec namespace, we need to delegate
132 # the lookup of names in __builtin__ to itself. This means
133 # the lookup of names in __builtin__ to itself. This means
133 # that you can't have section or attribute names that are
134 # that you can't have section or attribute names that are
134 # builtins.
135 # builtins.
135 try:
136 try:
136 return getattr(builtin_mod, key)
137 return getattr(builtin_mod, key)
137 except AttributeError:
138 except AttributeError:
138 pass
139 pass
139 if is_section_key(key):
140 if is_section_key(key):
140 try:
141 try:
141 return dict.__getitem__(self, key)
142 return dict.__getitem__(self, key)
142 except KeyError:
143 except KeyError:
143 c = Config()
144 c = Config()
144 dict.__setitem__(self, key, c)
145 dict.__setitem__(self, key, c)
145 return c
146 return c
146 else:
147 else:
147 return dict.__getitem__(self, key)
148 return dict.__getitem__(self, key)
148
149
149 def __setitem__(self, key, value):
150 def __setitem__(self, key, value):
150 # Don't allow names in __builtin__ to be modified.
151 # Don't allow names in __builtin__ to be modified.
151 if hasattr(builtin_mod, key):
152 if hasattr(builtin_mod, key):
152 raise ConfigError('Config variable names cannot have the same name '
153 raise ConfigError('Config variable names cannot have the same name '
153 'as a Python builtin: %s' % key)
154 'as a Python builtin: %s' % key)
154 if self._is_section_key(key):
155 if self._is_section_key(key):
155 if not isinstance(value, Config):
156 if not isinstance(value, Config):
156 raise ValueError('values whose keys begin with an uppercase '
157 raise ValueError('values whose keys begin with an uppercase '
157 'char must be Config instances: %r, %r' % (key, value))
158 'char must be Config instances: %r, %r' % (key, value))
158 else:
159 else:
159 dict.__setitem__(self, key, value)
160 dict.__setitem__(self, key, value)
160
161
161 def __getattr__(self, key):
162 def __getattr__(self, key):
162 try:
163 try:
163 return self.__getitem__(key)
164 return self.__getitem__(key)
164 except KeyError, e:
165 except KeyError, e:
165 raise AttributeError(e)
166 raise AttributeError(e)
166
167
167 def __setattr__(self, key, value):
168 def __setattr__(self, key, value):
168 try:
169 try:
169 self.__setitem__(key, value)
170 self.__setitem__(key, value)
170 except KeyError, e:
171 except KeyError, e:
171 raise AttributeError(e)
172 raise AttributeError(e)
172
173
173 def __delattr__(self, key):
174 def __delattr__(self, key):
174 try:
175 try:
175 dict.__delitem__(self, key)
176 dict.__delitem__(self, key)
176 except KeyError, e:
177 except KeyError, e:
177 raise AttributeError(e)
178 raise AttributeError(e)
178
179
179
180
180 #-----------------------------------------------------------------------------
181 #-----------------------------------------------------------------------------
181 # Config loading classes
182 # Config loading classes
182 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
183
184
184
185
185 class ConfigLoader(object):
186 class ConfigLoader(object):
186 """A object for loading configurations from just about anywhere.
187 """A object for loading configurations from just about anywhere.
187
188
188 The resulting configuration is packaged as a :class:`Struct`.
189 The resulting configuration is packaged as a :class:`Struct`.
189
190
190 Notes
191 Notes
191 -----
192 -----
192 A :class:`ConfigLoader` does one thing: load a config from a source
193 A :class:`ConfigLoader` does one thing: load a config from a source
193 (file, command line arguments) and returns the data as a :class:`Struct`.
194 (file, command line arguments) and returns the data as a :class:`Struct`.
194 There are lots of things that :class:`ConfigLoader` does not do. It does
195 There are lots of things that :class:`ConfigLoader` does not do. It does
195 not implement complex logic for finding config files. It does not handle
196 not implement complex logic for finding config files. It does not handle
196 default values or merge multiple configs. These things need to be
197 default values or merge multiple configs. These things need to be
197 handled elsewhere.
198 handled elsewhere.
198 """
199 """
199
200
200 def __init__(self):
201 def __init__(self):
201 """A base class for config loaders.
202 """A base class for config loaders.
202
203
203 Examples
204 Examples
204 --------
205 --------
205
206
206 >>> cl = ConfigLoader()
207 >>> cl = ConfigLoader()
207 >>> config = cl.load_config()
208 >>> config = cl.load_config()
208 >>> config
209 >>> config
209 {}
210 {}
210 """
211 """
211 self.clear()
212 self.clear()
212
213
213 def clear(self):
214 def clear(self):
214 self.config = Config()
215 self.config = Config()
215
216
216 def load_config(self):
217 def load_config(self):
217 """Load a config from somewhere, return a :class:`Config` instance.
218 """Load a config from somewhere, return a :class:`Config` instance.
218
219
219 Usually, this will cause self.config to be set and then returned.
220 Usually, this will cause self.config to be set and then returned.
220 However, in most cases, :meth:`ConfigLoader.clear` should be called
221 However, in most cases, :meth:`ConfigLoader.clear` should be called
221 to erase any previous state.
222 to erase any previous state.
222 """
223 """
223 self.clear()
224 self.clear()
224 return self.config
225 return self.config
225
226
226
227
227 class FileConfigLoader(ConfigLoader):
228 class FileConfigLoader(ConfigLoader):
228 """A base class for file based configurations.
229 """A base class for file based configurations.
229
230
230 As we add more file based config loaders, the common logic should go
231 As we add more file based config loaders, the common logic should go
231 here.
232 here.
232 """
233 """
233 pass
234 pass
234
235
235
236
236 class PyFileConfigLoader(FileConfigLoader):
237 class PyFileConfigLoader(FileConfigLoader):
237 """A config loader for pure python files.
238 """A config loader for pure python files.
238
239
239 This calls execfile on a plain python file and looks for attributes
240 This calls execfile on a plain python file and looks for attributes
240 that are all caps. These attribute are added to the config Struct.
241 that are all caps. These attribute are added to the config Struct.
241 """
242 """
242
243
243 def __init__(self, filename, path=None):
244 def __init__(self, filename, path=None):
244 """Build a config loader for a filename and path.
245 """Build a config loader for a filename and path.
245
246
246 Parameters
247 Parameters
247 ----------
248 ----------
248 filename : str
249 filename : str
249 The file name of the config file.
250 The file name of the config file.
250 path : str, list, tuple
251 path : str, list, tuple
251 The path to search for the config file on, or a sequence of
252 The path to search for the config file on, or a sequence of
252 paths to try in order.
253 paths to try in order.
253 """
254 """
254 super(PyFileConfigLoader, self).__init__()
255 super(PyFileConfigLoader, self).__init__()
255 self.filename = filename
256 self.filename = filename
256 self.path = path
257 self.path = path
257 self.full_filename = ''
258 self.full_filename = ''
258 self.data = None
259 self.data = None
259
260
260 def load_config(self):
261 def load_config(self):
261 """Load the config from a file and return it as a Struct."""
262 """Load the config from a file and return it as a Struct."""
262 self.clear()
263 self.clear()
263 try:
264 try:
264 self._find_file()
265 self._find_file()
265 except IOError as e:
266 except IOError as e:
266 raise ConfigFileNotFound(str(e))
267 raise ConfigFileNotFound(str(e))
267 self._read_file_as_dict()
268 self._read_file_as_dict()
268 self._convert_to_config()
269 self._convert_to_config()
269 return self.config
270 return self.config
270
271
271 def _find_file(self):
272 def _find_file(self):
272 """Try to find the file by searching the paths."""
273 """Try to find the file by searching the paths."""
273 self.full_filename = filefind(self.filename, self.path)
274 self.full_filename = filefind(self.filename, self.path)
274
275
275 def _read_file_as_dict(self):
276 def _read_file_as_dict(self):
276 """Load the config file into self.config, with recursive loading."""
277 """Load the config file into self.config, with recursive loading."""
277 # This closure is made available in the namespace that is used
278 # This closure is made available in the namespace that is used
278 # to exec the config file. It allows users to call
279 # to exec the config file. It allows users to call
279 # load_subconfig('myconfig.py') to load config files recursively.
280 # load_subconfig('myconfig.py') to load config files recursively.
280 # It needs to be a closure because it has references to self.path
281 # It needs to be a closure because it has references to self.path
281 # and self.config. The sub-config is loaded with the same path
282 # and self.config. The sub-config is loaded with the same path
282 # as the parent, but it uses an empty config which is then merged
283 # as the parent, but it uses an empty config which is then merged
283 # with the parents.
284 # with the parents.
284
285
285 # If a profile is specified, the config file will be loaded
286 # If a profile is specified, the config file will be loaded
286 # from that profile
287 # from that profile
287
288
288 def load_subconfig(fname, profile=None):
289 def load_subconfig(fname, profile=None):
289 # import here to prevent circular imports
290 # import here to prevent circular imports
290 from IPython.core.profiledir import ProfileDir, ProfileDirError
291 from IPython.core.profiledir import ProfileDir, ProfileDirError
291 if profile is not None:
292 if profile is not None:
292 try:
293 try:
293 profile_dir = ProfileDir.find_profile_dir_by_name(
294 profile_dir = ProfileDir.find_profile_dir_by_name(
294 get_ipython_dir(),
295 get_ipython_dir(),
295 profile,
296 profile,
296 )
297 )
297 except ProfileDirError:
298 except ProfileDirError:
298 return
299 return
299 path = profile_dir.location
300 path = profile_dir.location
300 else:
301 else:
301 path = self.path
302 path = self.path
302 loader = PyFileConfigLoader(fname, path)
303 loader = PyFileConfigLoader(fname, path)
303 try:
304 try:
304 sub_config = loader.load_config()
305 sub_config = loader.load_config()
305 except ConfigFileNotFound:
306 except ConfigFileNotFound:
306 # Pass silently if the sub config is not there. This happens
307 # Pass silently if the sub config is not there. This happens
307 # when a user s using a profile, but not the default config.
308 # when a user s using a profile, but not the default config.
308 pass
309 pass
309 else:
310 else:
310 self.config._merge(sub_config)
311 self.config._merge(sub_config)
311
312
312 # Again, this needs to be a closure and should be used in config
313 # Again, this needs to be a closure and should be used in config
313 # files to get the config being loaded.
314 # files to get the config being loaded.
314 def get_config():
315 def get_config():
315 return self.config
316 return self.config
316
317
317 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
318 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
318 fs_encoding = sys.getfilesystemencoding() or 'ascii'
319 fs_encoding = sys.getfilesystemencoding() or 'ascii'
319 conf_filename = self.full_filename.encode(fs_encoding)
320 conf_filename = self.full_filename.encode(fs_encoding)
320 py3compat.execfile(conf_filename, namespace)
321 py3compat.execfile(conf_filename, namespace)
321
322
322 def _convert_to_config(self):
323 def _convert_to_config(self):
323 if self.data is None:
324 if self.data is None:
324 ConfigLoaderError('self.data does not exist')
325 ConfigLoaderError('self.data does not exist')
325
326
326
327
327 class CommandLineConfigLoader(ConfigLoader):
328 class CommandLineConfigLoader(ConfigLoader):
328 """A config loader for command line arguments.
329 """A config loader for command line arguments.
329
330
330 As we add more command line based loaders, the common logic should go
331 As we add more command line based loaders, the common logic should go
331 here.
332 here.
332 """
333 """
333
334
334 def _exec_config_str(self, lhs, rhs):
335 def _exec_config_str(self, lhs, rhs):
336 """execute self.config.<lhs>=<rhs>
337
338 * expands ~ with expanduser
339 * tries to assign with raw exec, otherwise assigns with just the string,
340 allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent. *Not*
341 equivalent are `--C.a=4` and `--C.a='4'`.
342 """
343 rhs = os.path.expanduser(rhs)
335 exec_str = 'self.config.' + lhs + '=' + rhs
344 exec_str = 'self.config.' + lhs + '=' + rhs
336 try:
345 try:
337 # Try to see if regular Python syntax will work. This
346 # Try to see if regular Python syntax will work. This
338 # won't handle strings as the quote marks are removed
347 # won't handle strings as the quote marks are removed
339 # by the system shell.
348 # by the system shell.
340 exec exec_str in locals(), globals()
349 exec exec_str in locals(), globals()
341 except (NameError, SyntaxError):
350 except (NameError, SyntaxError):
342 # This case happens if the rhs is a string but without
351 # This case happens if the rhs is a string but without
343 # the quote marks. Use repr, to get quote marks, and
352 # the quote marks. Use repr, to get quote marks, and
344 # 'u' prefix and see if
353 # 'u' prefix and see if
345 # it succeeds. If it still fails, we let it raise.
354 # it succeeds. If it still fails, we let it raise.
346 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
355 exec_str = u'self.config.' + lhs + '= rhs'
347 exec exec_str in locals(), globals()
356 exec exec_str in locals(), globals()
348
357
349 def _load_flag(self, cfg):
358 def _load_flag(self, cfg):
350 """update self.config from a flag, which can be a dict or Config"""
359 """update self.config from a flag, which can be a dict or Config"""
351 if isinstance(cfg, (dict, Config)):
360 if isinstance(cfg, (dict, Config)):
352 # don't clobber whole config sections, update
361 # don't clobber whole config sections, update
353 # each section from config:
362 # each section from config:
354 for sec,c in cfg.iteritems():
363 for sec,c in cfg.iteritems():
355 self.config[sec].update(c)
364 self.config[sec].update(c)
356 else:
365 else:
357 raise ValueError("Invalid flag: '%s'"%raw)
366 raise ValueError("Invalid flag: '%s'"%raw)
358
367
359 # raw --identifier=value pattern
368 # raw --identifier=value pattern
360 # but *also* accept '-' as wordsep, for aliases
369 # but *also* accept '-' as wordsep, for aliases
361 # accepts: --foo=a
370 # accepts: --foo=a
362 # --Class.trait=value
371 # --Class.trait=value
363 # --alias-name=value
372 # --alias-name=value
364 # rejects: -foo=value
373 # rejects: -foo=value
365 # --foo
374 # --foo
366 # --Class.trait
375 # --Class.trait
367 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
376 kv_pattern = re.compile(r'\-\-[A-Za-z][\w\-]*(\.[\w\-]+)*\=.*')
368
377
369 # just flags, no assignments, with two *or one* leading '-'
378 # just flags, no assignments, with two *or one* leading '-'
370 # accepts: --foo
379 # accepts: --foo
371 # -foo-bar-again
380 # -foo-bar-again
372 # rejects: --anything=anything
381 # rejects: --anything=anything
373 # --two.word
382 # --two.word
374
383
375 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
384 flag_pattern = re.compile(r'\-\-?\w+[\-\w]*$')
376
385
377 class KeyValueConfigLoader(CommandLineConfigLoader):
386 class KeyValueConfigLoader(CommandLineConfigLoader):
378 """A config loader that loads key value pairs from the command line.
387 """A config loader that loads key value pairs from the command line.
379
388
380 This allows command line options to be gives in the following form::
389 This allows command line options to be gives in the following form::
381
390
382 ipython --profile="foo" --InteractiveShell.autocall=False
391 ipython --profile="foo" --InteractiveShell.autocall=False
383 """
392 """
384
393
385 def __init__(self, argv=None, aliases=None, flags=None):
394 def __init__(self, argv=None, aliases=None, flags=None):
386 """Create a key value pair config loader.
395 """Create a key value pair config loader.
387
396
388 Parameters
397 Parameters
389 ----------
398 ----------
390 argv : list
399 argv : list
391 A list that has the form of sys.argv[1:] which has unicode
400 A list that has the form of sys.argv[1:] which has unicode
392 elements of the form u"key=value". If this is None (default),
401 elements of the form u"key=value". If this is None (default),
393 then sys.argv[1:] will be used.
402 then sys.argv[1:] will be used.
394 aliases : dict
403 aliases : dict
395 A dict of aliases for configurable traits.
404 A dict of aliases for configurable traits.
396 Keys are the short aliases, Values are the resolved trait.
405 Keys are the short aliases, Values are the resolved trait.
397 Of the form: `{'alias' : 'Configurable.trait'}`
406 Of the form: `{'alias' : 'Configurable.trait'}`
398 flags : dict
407 flags : dict
399 A dict of flags, keyed by str name. Vaues can be Config objects,
408 A dict of flags, keyed by str name. Vaues can be Config objects,
400 dicts, or "key=value" strings. If Config or dict, when the flag
409 dicts, or "key=value" strings. If Config or dict, when the flag
401 is triggered, The flag is loaded as `self.config.update(m)`.
410 is triggered, The flag is loaded as `self.config.update(m)`.
402
411
403 Returns
412 Returns
404 -------
413 -------
405 config : Config
414 config : Config
406 The resulting Config object.
415 The resulting Config object.
407
416
408 Examples
417 Examples
409 --------
418 --------
410
419
411 >>> from IPython.config.loader import KeyValueConfigLoader
420 >>> from IPython.config.loader import KeyValueConfigLoader
412 >>> cl = KeyValueConfigLoader()
421 >>> cl = KeyValueConfigLoader()
413 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
422 >>> cl.load_config(["--A.name='brian'","--B.number=0"])
414 {'A': {'name': 'brian'}, 'B': {'number': 0}}
423 {'A': {'name': 'brian'}, 'B': {'number': 0}}
415 """
424 """
416 self.clear()
425 self.clear()
417 if argv is None:
426 if argv is None:
418 argv = sys.argv[1:]
427 argv = sys.argv[1:]
419 self.argv = argv
428 self.argv = argv
420 self.aliases = aliases or {}
429 self.aliases = aliases or {}
421 self.flags = flags or {}
430 self.flags = flags or {}
422
431
423
432
424 def clear(self):
433 def clear(self):
425 super(KeyValueConfigLoader, self).clear()
434 super(KeyValueConfigLoader, self).clear()
426 self.extra_args = []
435 self.extra_args = []
427
436
428
437
429 def _decode_argv(self, argv, enc=None):
438 def _decode_argv(self, argv, enc=None):
430 """decode argv if bytes, using stin.encoding, falling back on default enc"""
439 """decode argv if bytes, using stin.encoding, falling back on default enc"""
431 uargv = []
440 uargv = []
432 if enc is None:
441 if enc is None:
433 enc = text.getdefaultencoding()
442 enc = text.getdefaultencoding()
434 for arg in argv:
443 for arg in argv:
435 if not isinstance(arg, unicode):
444 if not isinstance(arg, unicode):
436 # only decode if not already decoded
445 # only decode if not already decoded
437 arg = arg.decode(enc)
446 arg = arg.decode(enc)
438 uargv.append(arg)
447 uargv.append(arg)
439 return uargv
448 return uargv
440
449
441
450
442 def load_config(self, argv=None, aliases=None, flags=None):
451 def load_config(self, argv=None, aliases=None, flags=None):
443 """Parse the configuration and generate the Config object.
452 """Parse the configuration and generate the Config object.
444
453
445 After loading, any arguments that are not key-value or
454 After loading, any arguments that are not key-value or
446 flags will be stored in self.extra_args - a list of
455 flags will be stored in self.extra_args - a list of
447 unparsed command-line arguments. This is used for
456 unparsed command-line arguments. This is used for
448 arguments such as input files or subcommands.
457 arguments such as input files or subcommands.
449
458
450 Parameters
459 Parameters
451 ----------
460 ----------
452 argv : list, optional
461 argv : list, optional
453 A list that has the form of sys.argv[1:] which has unicode
462 A list that has the form of sys.argv[1:] which has unicode
454 elements of the form u"key=value". If this is None (default),
463 elements of the form u"key=value". If this is None (default),
455 then self.argv will be used.
464 then self.argv will be used.
456 aliases : dict
465 aliases : dict
457 A dict of aliases for configurable traits.
466 A dict of aliases for configurable traits.
458 Keys are the short aliases, Values are the resolved trait.
467 Keys are the short aliases, Values are the resolved trait.
459 Of the form: `{'alias' : 'Configurable.trait'}`
468 Of the form: `{'alias' : 'Configurable.trait'}`
460 flags : dict
469 flags : dict
461 A dict of flags, keyed by str name. Values can be Config objects
470 A dict of flags, keyed by str name. Values can be Config objects
462 or dicts. When the flag is triggered, The config is loaded as
471 or dicts. When the flag is triggered, The config is loaded as
463 `self.config.update(cfg)`.
472 `self.config.update(cfg)`.
464 """
473 """
465 from IPython.config.configurable import Configurable
474 from IPython.config.configurable import Configurable
466
475
467 self.clear()
476 self.clear()
468 if argv is None:
477 if argv is None:
469 argv = self.argv
478 argv = self.argv
470 if aliases is None:
479 if aliases is None:
471 aliases = self.aliases
480 aliases = self.aliases
472 if flags is None:
481 if flags is None:
473 flags = self.flags
482 flags = self.flags
474
483
475 # ensure argv is a list of unicode strings:
484 # ensure argv is a list of unicode strings:
476 uargv = self._decode_argv(argv)
485 uargv = self._decode_argv(argv)
477 for idx,raw in enumerate(uargv):
486 for idx,raw in enumerate(uargv):
478 # strip leading '-'
487 # strip leading '-'
479 item = raw.lstrip('-')
488 item = raw.lstrip('-')
480
489
481 if raw == '--':
490 if raw == '--':
482 # don't parse arguments after '--'
491 # don't parse arguments after '--'
483 # this is useful for relaying arguments to scripts, e.g.
492 # this is useful for relaying arguments to scripts, e.g.
484 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
493 # ipython -i foo.py --pylab=qt -- args after '--' go-to-foo.py
485 self.extra_args.extend(uargv[idx+1:])
494 self.extra_args.extend(uargv[idx+1:])
486 break
495 break
487
496
488 if kv_pattern.match(raw):
497 if kv_pattern.match(raw):
489 lhs,rhs = item.split('=',1)
498 lhs,rhs = item.split('=',1)
490 # Substitute longnames for aliases.
499 # Substitute longnames for aliases.
491 if lhs in aliases:
500 if lhs in aliases:
492 lhs = aliases[lhs]
501 lhs = aliases[lhs]
493 if '.' not in lhs:
502 if '.' not in lhs:
494 # probably a mistyped alias, but not technically illegal
503 # probably a mistyped alias, but not technically illegal
495 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
504 warn.warn("Unrecognized alias: '%s', it will probably have no effect."%lhs)
496 self._exec_config_str(lhs, rhs)
505 self._exec_config_str(lhs, rhs)
497
506
498 elif flag_pattern.match(raw):
507 elif flag_pattern.match(raw):
499 if item in flags:
508 if item in flags:
500 cfg,help = flags[item]
509 cfg,help = flags[item]
501 self._load_flag(cfg)
510 self._load_flag(cfg)
502 else:
511 else:
503 raise ArgumentError("Unrecognized flag: '%s'"%raw)
512 raise ArgumentError("Unrecognized flag: '%s'"%raw)
504 elif raw.startswith('-'):
513 elif raw.startswith('-'):
505 kv = '--'+item
514 kv = '--'+item
506 if kv_pattern.match(kv):
515 if kv_pattern.match(kv):
507 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
516 raise ArgumentError("Invalid argument: '%s', did you mean '%s'?"%(raw, kv))
508 else:
517 else:
509 raise ArgumentError("Invalid argument: '%s'"%raw)
518 raise ArgumentError("Invalid argument: '%s'"%raw)
510 else:
519 else:
511 # keep all args that aren't valid in a list,
520 # keep all args that aren't valid in a list,
512 # in case our parent knows what to do with them.
521 # in case our parent knows what to do with them.
513 self.extra_args.append(item)
522 self.extra_args.append(item)
514 return self.config
523 return self.config
515
524
516 class ArgParseConfigLoader(CommandLineConfigLoader):
525 class ArgParseConfigLoader(CommandLineConfigLoader):
517 """A loader that uses the argparse module to load from the command line."""
526 """A loader that uses the argparse module to load from the command line."""
518
527
519 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
528 def __init__(self, argv=None, aliases=None, flags=None, *parser_args, **parser_kw):
520 """Create a config loader for use with argparse.
529 """Create a config loader for use with argparse.
521
530
522 Parameters
531 Parameters
523 ----------
532 ----------
524
533
525 argv : optional, list
534 argv : optional, list
526 If given, used to read command-line arguments from, otherwise
535 If given, used to read command-line arguments from, otherwise
527 sys.argv[1:] is used.
536 sys.argv[1:] is used.
528
537
529 parser_args : tuple
538 parser_args : tuple
530 A tuple of positional arguments that will be passed to the
539 A tuple of positional arguments that will be passed to the
531 constructor of :class:`argparse.ArgumentParser`.
540 constructor of :class:`argparse.ArgumentParser`.
532
541
533 parser_kw : dict
542 parser_kw : dict
534 A tuple of keyword arguments that will be passed to the
543 A tuple of keyword arguments that will be passed to the
535 constructor of :class:`argparse.ArgumentParser`.
544 constructor of :class:`argparse.ArgumentParser`.
536
545
537 Returns
546 Returns
538 -------
547 -------
539 config : Config
548 config : Config
540 The resulting Config object.
549 The resulting Config object.
541 """
550 """
542 super(CommandLineConfigLoader, self).__init__()
551 super(CommandLineConfigLoader, self).__init__()
543 self.clear()
552 self.clear()
544 if argv is None:
553 if argv is None:
545 argv = sys.argv[1:]
554 argv = sys.argv[1:]
546 self.argv = argv
555 self.argv = argv
547 self.aliases = aliases or {}
556 self.aliases = aliases or {}
548 self.flags = flags or {}
557 self.flags = flags or {}
549
558
550 self.parser_args = parser_args
559 self.parser_args = parser_args
551 self.version = parser_kw.pop("version", None)
560 self.version = parser_kw.pop("version", None)
552 kwargs = dict(argument_default=argparse.SUPPRESS)
561 kwargs = dict(argument_default=argparse.SUPPRESS)
553 kwargs.update(parser_kw)
562 kwargs.update(parser_kw)
554 self.parser_kw = kwargs
563 self.parser_kw = kwargs
555
564
556 def load_config(self, argv=None, aliases=None, flags=None):
565 def load_config(self, argv=None, aliases=None, flags=None):
557 """Parse command line arguments and return as a Config object.
566 """Parse command line arguments and return as a Config object.
558
567
559 Parameters
568 Parameters
560 ----------
569 ----------
561
570
562 args : optional, list
571 args : optional, list
563 If given, a list with the structure of sys.argv[1:] to parse
572 If given, a list with the structure of sys.argv[1:] to parse
564 arguments from. If not given, the instance's self.argv attribute
573 arguments from. If not given, the instance's self.argv attribute
565 (given at construction time) is used."""
574 (given at construction time) is used."""
566 self.clear()
575 self.clear()
567 if argv is None:
576 if argv is None:
568 argv = self.argv
577 argv = self.argv
569 if aliases is None:
578 if aliases is None:
570 aliases = self.aliases
579 aliases = self.aliases
571 if flags is None:
580 if flags is None:
572 flags = self.flags
581 flags = self.flags
573 self._create_parser(aliases, flags)
582 self._create_parser(aliases, flags)
574 self._parse_args(argv)
583 self._parse_args(argv)
575 self._convert_to_config()
584 self._convert_to_config()
576 return self.config
585 return self.config
577
586
578 def get_extra_args(self):
587 def get_extra_args(self):
579 if hasattr(self, 'extra_args'):
588 if hasattr(self, 'extra_args'):
580 return self.extra_args
589 return self.extra_args
581 else:
590 else:
582 return []
591 return []
583
592
584 def _create_parser(self, aliases=None, flags=None):
593 def _create_parser(self, aliases=None, flags=None):
585 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
594 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
586 self._add_arguments(aliases, flags)
595 self._add_arguments(aliases, flags)
587
596
588 def _add_arguments(self, aliases=None, flags=None):
597 def _add_arguments(self, aliases=None, flags=None):
589 raise NotImplementedError("subclasses must implement _add_arguments")
598 raise NotImplementedError("subclasses must implement _add_arguments")
590
599
591 def _parse_args(self, args):
600 def _parse_args(self, args):
592 """self.parser->self.parsed_data"""
601 """self.parser->self.parsed_data"""
593 # decode sys.argv to support unicode command-line options
602 # decode sys.argv to support unicode command-line options
594 enc = text.getdefaultencoding()
603 enc = text.getdefaultencoding()
595 uargs = [py3compat.cast_unicode(a, enc) for a in args]
604 uargs = [py3compat.cast_unicode(a, enc) for a in args]
596 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
605 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
597
606
598 def _convert_to_config(self):
607 def _convert_to_config(self):
599 """self.parsed_data->self.config"""
608 """self.parsed_data->self.config"""
600 for k, v in vars(self.parsed_data).iteritems():
609 for k, v in vars(self.parsed_data).iteritems():
601 exec "self.config.%s = v"%k in locals(), globals()
610 exec "self.config.%s = v"%k in locals(), globals()
602
611
603 class KVArgParseConfigLoader(ArgParseConfigLoader):
612 class KVArgParseConfigLoader(ArgParseConfigLoader):
604 """A config loader that loads aliases and flags with argparse,
613 """A config loader that loads aliases and flags with argparse,
605 but will use KVLoader for the rest. This allows better parsing
614 but will use KVLoader for the rest. This allows better parsing
606 of common args, such as `ipython -c 'print 5'`, but still gets
615 of common args, such as `ipython -c 'print 5'`, but still gets
607 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
616 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
608
617
609 def _convert_to_config(self):
618 def _convert_to_config(self):
610 """self.parsed_data->self.config"""
619 """self.parsed_data->self.config"""
611 for k, v in vars(self.parsed_data).iteritems():
620 for k, v in vars(self.parsed_data).iteritems():
612 self._exec_config_str(k, v)
621 self._exec_config_str(k, v)
613
622
614 def _add_arguments(self, aliases=None, flags=None):
623 def _add_arguments(self, aliases=None, flags=None):
615 self.alias_flags = {}
624 self.alias_flags = {}
616 # print aliases, flags
625 # print aliases, flags
617 if aliases is None:
626 if aliases is None:
618 aliases = self.aliases
627 aliases = self.aliases
619 if flags is None:
628 if flags is None:
620 flags = self.flags
629 flags = self.flags
621 paa = self.parser.add_argument
630 paa = self.parser.add_argument
622 for key,value in aliases.iteritems():
631 for key,value in aliases.iteritems():
623 if key in flags:
632 if key in flags:
624 # flags
633 # flags
625 nargs = '?'
634 nargs = '?'
626 else:
635 else:
627 nargs = None
636 nargs = None
628 if len(key) is 1:
637 if len(key) is 1:
629 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
638 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
630 else:
639 else:
631 paa('--'+key, type=unicode, dest=value, nargs=nargs)
640 paa('--'+key, type=unicode, dest=value, nargs=nargs)
632 for key, (value, help) in flags.iteritems():
641 for key, (value, help) in flags.iteritems():
633 if key in self.aliases:
642 if key in self.aliases:
634 #
643 #
635 self.alias_flags[self.aliases[key]] = value
644 self.alias_flags[self.aliases[key]] = value
636 continue
645 continue
637 if len(key) is 1:
646 if len(key) is 1:
638 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
647 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
639 else:
648 else:
640 paa('--'+key, action='append_const', dest='_flags', const=value)
649 paa('--'+key, action='append_const', dest='_flags', const=value)
641
650
642 def _convert_to_config(self):
651 def _convert_to_config(self):
643 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
652 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
644 # remove subconfigs list from namespace before transforming the Namespace
653 # remove subconfigs list from namespace before transforming the Namespace
645 if '_flags' in self.parsed_data:
654 if '_flags' in self.parsed_data:
646 subcs = self.parsed_data._flags
655 subcs = self.parsed_data._flags
647 del self.parsed_data._flags
656 del self.parsed_data._flags
648 else:
657 else:
649 subcs = []
658 subcs = []
650
659
651 for k, v in vars(self.parsed_data).iteritems():
660 for k, v in vars(self.parsed_data).iteritems():
652 if v is None:
661 if v is None:
653 # it was a flag that shares the name of an alias
662 # it was a flag that shares the name of an alias
654 subcs.append(self.alias_flags[k])
663 subcs.append(self.alias_flags[k])
655 else:
664 else:
656 # eval the KV assignment
665 # eval the KV assignment
657 self._exec_config_str(k, v)
666 self._exec_config_str(k, v)
658
667
659 for subc in subcs:
668 for subc in subcs:
660 self._load_flag(subc)
669 self._load_flag(subc)
661
670
662 if self.extra_args:
671 if self.extra_args:
663 sub_parser = KeyValueConfigLoader()
672 sub_parser = KeyValueConfigLoader()
664 sub_parser.load_config(self.extra_args)
673 sub_parser.load_config(self.extra_args)
665 self.config._merge(sub_parser.config)
674 self.config._merge(sub_parser.config)
666 self.extra_args = sub_parser.extra_args
675 self.extra_args = sub_parser.extra_args
@@ -1,237 +1,255 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for IPython.config.loader
3 Tests for IPython.config.loader
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez (design help)
8 * Fernando Perez (design help)
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2009 The IPython Development Team
12 # Copyright (C) 2008-2009 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import sys
23 import sys
24 from tempfile import mkstemp
24 from tempfile import mkstemp
25 from unittest import TestCase
25 from unittest import TestCase
26
26
27 from nose import SkipTest
27 from nose import SkipTest
28
28
29 from IPython.testing.tools import mute_warn
29 from IPython.testing.tools import mute_warn
30
30
31 from IPython.utils.traitlets import Int, Unicode
31 from IPython.utils.traitlets import Int, Unicode
32 from IPython.config.configurable import Configurable
32 from IPython.config.configurable import Configurable
33 from IPython.config.loader import (
33 from IPython.config.loader import (
34 Config,
34 Config,
35 PyFileConfigLoader,
35 PyFileConfigLoader,
36 KeyValueConfigLoader,
36 KeyValueConfigLoader,
37 ArgParseConfigLoader,
37 ArgParseConfigLoader,
38 KVArgParseConfigLoader,
38 KVArgParseConfigLoader,
39 ConfigError
39 ConfigError
40 )
40 )
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Actual tests
43 # Actual tests
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46
46
47 pyfile = """
47 pyfile = """
48 c = get_config()
48 c = get_config()
49 c.a=10
49 c.a=10
50 c.b=20
50 c.b=20
51 c.Foo.Bar.value=10
51 c.Foo.Bar.value=10
52 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3
52 c.Foo.Bam.value=list(range(10)) # list() is just so it's the same on Python 3
53 c.D.C.value='hi there'
53 c.D.C.value='hi there'
54 """
54 """
55
55
56 class TestPyFileCL(TestCase):
56 class TestPyFileCL(TestCase):
57
57
58 def test_basic(self):
58 def test_basic(self):
59 fd, fname = mkstemp('.py')
59 fd, fname = mkstemp('.py')
60 f = os.fdopen(fd, 'w')
60 f = os.fdopen(fd, 'w')
61 f.write(pyfile)
61 f.write(pyfile)
62 f.close()
62 f.close()
63 # Unlink the file
63 # Unlink the file
64 cl = PyFileConfigLoader(fname)
64 cl = PyFileConfigLoader(fname)
65 config = cl.load_config()
65 config = cl.load_config()
66 self.assertEquals(config.a, 10)
66 self.assertEquals(config.a, 10)
67 self.assertEquals(config.b, 20)
67 self.assertEquals(config.b, 20)
68 self.assertEquals(config.Foo.Bar.value, 10)
68 self.assertEquals(config.Foo.Bar.value, 10)
69 self.assertEquals(config.Foo.Bam.value, range(10))
69 self.assertEquals(config.Foo.Bam.value, range(10))
70 self.assertEquals(config.D.C.value, 'hi there')
70 self.assertEquals(config.D.C.value, 'hi there')
71
71
72 class MyLoader1(ArgParseConfigLoader):
72 class MyLoader1(ArgParseConfigLoader):
73 def _add_arguments(self, aliases=None, flags=None):
73 def _add_arguments(self, aliases=None, flags=None):
74 p = self.parser
74 p = self.parser
75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
76 p.add_argument('-b', dest='MyClass.bar', type=int)
76 p.add_argument('-b', dest='MyClass.bar', type=int)
77 p.add_argument('-n', dest='n', action='store_true')
77 p.add_argument('-n', dest='n', action='store_true')
78 p.add_argument('Global.bam', type=str)
78 p.add_argument('Global.bam', type=str)
79
79
80 class MyLoader2(ArgParseConfigLoader):
80 class MyLoader2(ArgParseConfigLoader):
81 def _add_arguments(self, aliases=None, flags=None):
81 def _add_arguments(self, aliases=None, flags=None):
82 subparsers = self.parser.add_subparsers(dest='subparser_name')
82 subparsers = self.parser.add_subparsers(dest='subparser_name')
83 subparser1 = subparsers.add_parser('1')
83 subparser1 = subparsers.add_parser('1')
84 subparser1.add_argument('-x',dest='Global.x')
84 subparser1.add_argument('-x',dest='Global.x')
85 subparser2 = subparsers.add_parser('2')
85 subparser2 = subparsers.add_parser('2')
86 subparser2.add_argument('y')
86 subparser2.add_argument('y')
87
87
88 class TestArgParseCL(TestCase):
88 class TestArgParseCL(TestCase):
89
89
90 def test_basic(self):
90 def test_basic(self):
91 cl = MyLoader1()
91 cl = MyLoader1()
92 config = cl.load_config('-f hi -b 10 -n wow'.split())
92 config = cl.load_config('-f hi -b 10 -n wow'.split())
93 self.assertEquals(config.Global.foo, 'hi')
93 self.assertEquals(config.Global.foo, 'hi')
94 self.assertEquals(config.MyClass.bar, 10)
94 self.assertEquals(config.MyClass.bar, 10)
95 self.assertEquals(config.n, True)
95 self.assertEquals(config.n, True)
96 self.assertEquals(config.Global.bam, 'wow')
96 self.assertEquals(config.Global.bam, 'wow')
97 config = cl.load_config(['wow'])
97 config = cl.load_config(['wow'])
98 self.assertEquals(config.keys(), ['Global'])
98 self.assertEquals(config.keys(), ['Global'])
99 self.assertEquals(config.Global.keys(), ['bam'])
99 self.assertEquals(config.Global.keys(), ['bam'])
100 self.assertEquals(config.Global.bam, 'wow')
100 self.assertEquals(config.Global.bam, 'wow')
101
101
102 def test_add_arguments(self):
102 def test_add_arguments(self):
103 cl = MyLoader2()
103 cl = MyLoader2()
104 config = cl.load_config('2 frobble'.split())
104 config = cl.load_config('2 frobble'.split())
105 self.assertEquals(config.subparser_name, '2')
105 self.assertEquals(config.subparser_name, '2')
106 self.assertEquals(config.y, 'frobble')
106 self.assertEquals(config.y, 'frobble')
107 config = cl.load_config('1 -x frobble'.split())
107 config = cl.load_config('1 -x frobble'.split())
108 self.assertEquals(config.subparser_name, '1')
108 self.assertEquals(config.subparser_name, '1')
109 self.assertEquals(config.Global.x, 'frobble')
109 self.assertEquals(config.Global.x, 'frobble')
110
110
111 def test_argv(self):
111 def test_argv(self):
112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
113 config = cl.load_config()
113 config = cl.load_config()
114 self.assertEquals(config.Global.foo, 'hi')
114 self.assertEquals(config.Global.foo, 'hi')
115 self.assertEquals(config.MyClass.bar, 10)
115 self.assertEquals(config.MyClass.bar, 10)
116 self.assertEquals(config.n, True)
116 self.assertEquals(config.n, True)
117 self.assertEquals(config.Global.bam, 'wow')
117 self.assertEquals(config.Global.bam, 'wow')
118
118
119
119
120 class TestKeyValueCL(TestCase):
120 class TestKeyValueCL(TestCase):
121 klass = KeyValueConfigLoader
121 klass = KeyValueConfigLoader
122
122
123 def test_basic(self):
123 def test_basic(self):
124 cl = self.klass()
124 cl = self.klass()
125 argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]]
125 argv = ['--'+s.strip('c.') for s in pyfile.split('\n')[2:-1]]
126 with mute_warn():
126 with mute_warn():
127 config = cl.load_config(argv)
127 config = cl.load_config(argv)
128 self.assertEquals(config.a, 10)
128 self.assertEquals(config.a, 10)
129 self.assertEquals(config.b, 20)
129 self.assertEquals(config.b, 20)
130 self.assertEquals(config.Foo.Bar.value, 10)
130 self.assertEquals(config.Foo.Bar.value, 10)
131 self.assertEquals(config.Foo.Bam.value, range(10))
131 self.assertEquals(config.Foo.Bam.value, range(10))
132 self.assertEquals(config.D.C.value, 'hi there')
132 self.assertEquals(config.D.C.value, 'hi there')
133
133
134 def test_expanduser(self):
135 cl = self.klass()
136 argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
137 with mute_warn():
138 config = cl.load_config(argv)
139 self.assertEquals(config.a, os.path.expanduser('~/1/2/3'))
140 self.assertEquals(config.b, os.path.expanduser('~'))
141 self.assertEquals(config.c, os.path.expanduser('~/'))
142 self.assertEquals(config.d, '~/')
143
134 def test_extra_args(self):
144 def test_extra_args(self):
135 cl = self.klass()
145 cl = self.klass()
136 with mute_warn():
146 with mute_warn():
137 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
147 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
138 self.assertEquals(cl.extra_args, ['b', 'd'])
148 self.assertEquals(cl.extra_args, ['b', 'd'])
139 self.assertEquals(config.a, 5)
149 self.assertEquals(config.a, 5)
140 self.assertEquals(config.c, 10)
150 self.assertEquals(config.c, 10)
141 with mute_warn():
151 with mute_warn():
142 config = cl.load_config(['--', '--a=5', '--c=10'])
152 config = cl.load_config(['--', '--a=5', '--c=10'])
143 self.assertEquals(cl.extra_args, ['--a=5', '--c=10'])
153 self.assertEquals(cl.extra_args, ['--a=5', '--c=10'])
144
154
145 def test_unicode_args(self):
155 def test_unicode_args(self):
146 cl = self.klass()
156 cl = self.klass()
147 argv = [u'--a=épsîlön']
157 argv = [u'--a=épsîlön']
148 with mute_warn():
158 with mute_warn():
149 config = cl.load_config(argv)
159 config = cl.load_config(argv)
150 self.assertEquals(config.a, u'épsîlön')
160 self.assertEquals(config.a, u'épsîlön')
151
161
152 def test_unicode_bytes_args(self):
162 def test_unicode_bytes_args(self):
153 uarg = u'--a=é'
163 uarg = u'--a=é'
154 try:
164 try:
155 barg = uarg.encode(sys.stdin.encoding)
165 barg = uarg.encode(sys.stdin.encoding)
156 except (TypeError, UnicodeEncodeError):
166 except (TypeError, UnicodeEncodeError):
157 raise SkipTest("sys.stdin.encoding can't handle 'é'")
167 raise SkipTest("sys.stdin.encoding can't handle 'é'")
158
168
159 cl = self.klass()
169 cl = self.klass()
160 with mute_warn():
170 with mute_warn():
161 config = cl.load_config([barg])
171 config = cl.load_config([barg])
162 self.assertEquals(config.a, u'é')
172 self.assertEquals(config.a, u'é')
163
173
164 def test_unicode_alias(self):
174 def test_unicode_alias(self):
165 cl = self.klass()
175 cl = self.klass()
166 argv = [u'--a=épsîlön']
176 argv = [u'--a=épsîlön']
167 with mute_warn():
177 with mute_warn():
168 config = cl.load_config(argv, aliases=dict(a='A.a'))
178 config = cl.load_config(argv, aliases=dict(a='A.a'))
169 self.assertEquals(config.A.a, u'épsîlön')
179 self.assertEquals(config.A.a, u'épsîlön')
170
180
171
181
172 class TestArgParseKVCL(TestKeyValueCL):
182 class TestArgParseKVCL(TestKeyValueCL):
173 klass = KVArgParseConfigLoader
183 klass = KVArgParseConfigLoader
174
184
185 def test_expanduser2(self):
186 cl = self.klass()
187 argv = ['-a', '~/1/2/3', '--b', "'~/1/2/3'"]
188 with mute_warn():
189 config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
190 self.assertEquals(config.A.a, os.path.expanduser('~/1/2/3'))
191 self.assertEquals(config.A.b, '~/1/2/3')
192
175 class TestConfig(TestCase):
193 class TestConfig(TestCase):
176
194
177 def test_setget(self):
195 def test_setget(self):
178 c = Config()
196 c = Config()
179 c.a = 10
197 c.a = 10
180 self.assertEquals(c.a, 10)
198 self.assertEquals(c.a, 10)
181 self.assertEquals(c.has_key('b'), False)
199 self.assertEquals(c.has_key('b'), False)
182
200
183 def test_auto_section(self):
201 def test_auto_section(self):
184 c = Config()
202 c = Config()
185 self.assertEquals(c.has_key('A'), True)
203 self.assertEquals(c.has_key('A'), True)
186 self.assertEquals(c._has_section('A'), False)
204 self.assertEquals(c._has_section('A'), False)
187 A = c.A
205 A = c.A
188 A.foo = 'hi there'
206 A.foo = 'hi there'
189 self.assertEquals(c._has_section('A'), True)
207 self.assertEquals(c._has_section('A'), True)
190 self.assertEquals(c.A.foo, 'hi there')
208 self.assertEquals(c.A.foo, 'hi there')
191 del c.A
209 del c.A
192 self.assertEquals(len(c.A.keys()),0)
210 self.assertEquals(len(c.A.keys()),0)
193
211
194 def test_merge_doesnt_exist(self):
212 def test_merge_doesnt_exist(self):
195 c1 = Config()
213 c1 = Config()
196 c2 = Config()
214 c2 = Config()
197 c2.bar = 10
215 c2.bar = 10
198 c2.Foo.bar = 10
216 c2.Foo.bar = 10
199 c1._merge(c2)
217 c1._merge(c2)
200 self.assertEquals(c1.Foo.bar, 10)
218 self.assertEquals(c1.Foo.bar, 10)
201 self.assertEquals(c1.bar, 10)
219 self.assertEquals(c1.bar, 10)
202 c2.Bar.bar = 10
220 c2.Bar.bar = 10
203 c1._merge(c2)
221 c1._merge(c2)
204 self.assertEquals(c1.Bar.bar, 10)
222 self.assertEquals(c1.Bar.bar, 10)
205
223
206 def test_merge_exists(self):
224 def test_merge_exists(self):
207 c1 = Config()
225 c1 = Config()
208 c2 = Config()
226 c2 = Config()
209 c1.Foo.bar = 10
227 c1.Foo.bar = 10
210 c1.Foo.bam = 30
228 c1.Foo.bam = 30
211 c2.Foo.bar = 20
229 c2.Foo.bar = 20
212 c2.Foo.wow = 40
230 c2.Foo.wow = 40
213 c1._merge(c2)
231 c1._merge(c2)
214 self.assertEquals(c1.Foo.bam, 30)
232 self.assertEquals(c1.Foo.bam, 30)
215 self.assertEquals(c1.Foo.bar, 20)
233 self.assertEquals(c1.Foo.bar, 20)
216 self.assertEquals(c1.Foo.wow, 40)
234 self.assertEquals(c1.Foo.wow, 40)
217 c2.Foo.Bam.bam = 10
235 c2.Foo.Bam.bam = 10
218 c1._merge(c2)
236 c1._merge(c2)
219 self.assertEquals(c1.Foo.Bam.bam, 10)
237 self.assertEquals(c1.Foo.Bam.bam, 10)
220
238
221 def test_deepcopy(self):
239 def test_deepcopy(self):
222 c1 = Config()
240 c1 = Config()
223 c1.Foo.bar = 10
241 c1.Foo.bar = 10
224 c1.Foo.bam = 30
242 c1.Foo.bam = 30
225 c1.a = 'asdf'
243 c1.a = 'asdf'
226 c1.b = range(10)
244 c1.b = range(10)
227 import copy
245 import copy
228 c2 = copy.deepcopy(c1)
246 c2 = copy.deepcopy(c1)
229 self.assertEquals(c1, c2)
247 self.assertEquals(c1, c2)
230 self.assert_(c1 is not c2)
248 self.assert_(c1 is not c2)
231 self.assert_(c1.Foo is not c2.Foo)
249 self.assert_(c1.Foo is not c2.Foo)
232
250
233 def test_builtin(self):
251 def test_builtin(self):
234 c1 = Config()
252 c1 = Config()
235 exec 'foo = True' in c1
253 exec 'foo = True' in c1
236 self.assertEquals(c1.foo, True)
254 self.assertEquals(c1.foo, True)
237 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
255 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
General Comments 0
You need to be logged in to leave comments. Login now