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