##// END OF EJS Templates
fix type=str->unicode in argparse kv loader...
MinRK -
Show More
@@ -1,661 +1,661 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 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 py3compat, text, warn
27 from IPython.utils import py3compat, text, 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_mod, key)
134 return getattr(builtin_mod, 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_mod, key):
149 if hasattr(builtin_mod, 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 py3compat.execfile(conf_filename, namespace)
315 py3compat.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 = text.getdefaultencoding()
428 enc = text.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 enc = text.getdefaultencoding()
589 enc = text.getdefaultencoding()
590 uargs = [py3compat.cast_unicode(a, enc) for a in args]
590 uargs = [py3compat.cast_unicode(a, enc) for a in args]
591 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
591 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
592
592
593 def _convert_to_config(self):
593 def _convert_to_config(self):
594 """self.parsed_data->self.config"""
594 """self.parsed_data->self.config"""
595 for k, v in vars(self.parsed_data).iteritems():
595 for k, v in vars(self.parsed_data).iteritems():
596 exec "self.config.%s = v"%k in locals(), globals()
596 exec "self.config.%s = v"%k in locals(), globals()
597
597
598 class KVArgParseConfigLoader(ArgParseConfigLoader):
598 class KVArgParseConfigLoader(ArgParseConfigLoader):
599 """A config loader that loads aliases and flags with argparse,
599 """A config loader that loads aliases and flags with argparse,
600 but will use KVLoader for the rest. This allows better parsing
600 but will use KVLoader for the rest. This allows better parsing
601 of common args, such as `ipython -c 'print 5'`, but still gets
601 of common args, such as `ipython -c 'print 5'`, but still gets
602 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
602 arbitrary config with `ipython --InteractiveShell.use_readline=False`"""
603
603
604 def _convert_to_config(self):
604 def _convert_to_config(self):
605 """self.parsed_data->self.config"""
605 """self.parsed_data->self.config"""
606 for k, v in vars(self.parsed_data).iteritems():
606 for k, v in vars(self.parsed_data).iteritems():
607 self._exec_config_str(k, v)
607 self._exec_config_str(k, v)
608
608
609 def _add_arguments(self, aliases=None, flags=None):
609 def _add_arguments(self, aliases=None, flags=None):
610 self.alias_flags = {}
610 self.alias_flags = {}
611 # print aliases, flags
611 # print aliases, flags
612 if aliases is None:
612 if aliases is None:
613 aliases = self.aliases
613 aliases = self.aliases
614 if flags is None:
614 if flags is None:
615 flags = self.flags
615 flags = self.flags
616 paa = self.parser.add_argument
616 paa = self.parser.add_argument
617 for key,value in aliases.iteritems():
617 for key,value in aliases.iteritems():
618 if key in flags:
618 if key in flags:
619 # flags
619 # flags
620 nargs = '?'
620 nargs = '?'
621 else:
621 else:
622 nargs = None
622 nargs = None
623 if len(key) is 1:
623 if len(key) is 1:
624 paa('-'+key, '--'+key, type=str, dest=value, nargs=nargs)
624 paa('-'+key, '--'+key, type=unicode, dest=value, nargs=nargs)
625 else:
625 else:
626 paa('--'+key, type=str, dest=value, nargs=nargs)
626 paa('--'+key, type=unicode, dest=value, nargs=nargs)
627 for key, (value, help) in flags.iteritems():
627 for key, (value, help) in flags.iteritems():
628 if key in self.aliases:
628 if key in self.aliases:
629 #
629 #
630 self.alias_flags[self.aliases[key]] = value
630 self.alias_flags[self.aliases[key]] = value
631 continue
631 continue
632 if len(key) is 1:
632 if len(key) is 1:
633 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
633 paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
634 else:
634 else:
635 paa('--'+key, action='append_const', dest='_flags', const=value)
635 paa('--'+key, action='append_const', dest='_flags', const=value)
636
636
637 def _convert_to_config(self):
637 def _convert_to_config(self):
638 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
638 """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
639 # remove subconfigs list from namespace before transforming the Namespace
639 # remove subconfigs list from namespace before transforming the Namespace
640 if '_flags' in self.parsed_data:
640 if '_flags' in self.parsed_data:
641 subcs = self.parsed_data._flags
641 subcs = self.parsed_data._flags
642 del self.parsed_data._flags
642 del self.parsed_data._flags
643 else:
643 else:
644 subcs = []
644 subcs = []
645
645
646 for k, v in vars(self.parsed_data).iteritems():
646 for k, v in vars(self.parsed_data).iteritems():
647 if v is None:
647 if v is None:
648 # it was a flag that shares the name of an alias
648 # it was a flag that shares the name of an alias
649 subcs.append(self.alias_flags[k])
649 subcs.append(self.alias_flags[k])
650 else:
650 else:
651 # eval the KV assignment
651 # eval the KV assignment
652 self._exec_config_str(k, v)
652 self._exec_config_str(k, v)
653
653
654 for subc in subcs:
654 for subc in subcs:
655 self._load_flag(subc)
655 self._load_flag(subc)
656
656
657 if self.extra_args:
657 if self.extra_args:
658 sub_parser = KeyValueConfigLoader()
658 sub_parser = KeyValueConfigLoader()
659 sub_parser.load_config(self.extra_args)
659 sub_parser.load_config(self.extra_args)
660 self.config._merge(sub_parser.config)
660 self.config._merge(sub_parser.config)
661 self.extra_args = sub_parser.extra_args
661 self.extra_args = sub_parser.extra_args
@@ -1,225 +1,237 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 ConfigError
39 ConfigError
39 )
40 )
40
41
41 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
42 # Actual tests
43 # Actual tests
43 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
44
45
45
46
46 pyfile = """
47 pyfile = """
47 c = get_config()
48 c = get_config()
48 c.a=10
49 c.a=10
49 c.b=20
50 c.b=20
50 c.Foo.Bar.value=10
51 c.Foo.Bar.value=10
51 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
52 c.D.C.value='hi there'
53 c.D.C.value='hi there'
53 """
54 """
54
55
55 class TestPyFileCL(TestCase):
56 class TestPyFileCL(TestCase):
56
57
57 def test_basic(self):
58 def test_basic(self):
58 fd, fname = mkstemp('.py')
59 fd, fname = mkstemp('.py')
59 f = os.fdopen(fd, 'w')
60 f = os.fdopen(fd, 'w')
60 f.write(pyfile)
61 f.write(pyfile)
61 f.close()
62 f.close()
62 # Unlink the file
63 # Unlink the file
63 cl = PyFileConfigLoader(fname)
64 cl = PyFileConfigLoader(fname)
64 config = cl.load_config()
65 config = cl.load_config()
65 self.assertEquals(config.a, 10)
66 self.assertEquals(config.a, 10)
66 self.assertEquals(config.b, 20)
67 self.assertEquals(config.b, 20)
67 self.assertEquals(config.Foo.Bar.value, 10)
68 self.assertEquals(config.Foo.Bar.value, 10)
68 self.assertEquals(config.Foo.Bam.value, range(10))
69 self.assertEquals(config.Foo.Bam.value, range(10))
69 self.assertEquals(config.D.C.value, 'hi there')
70 self.assertEquals(config.D.C.value, 'hi there')
70
71
71 class MyLoader1(ArgParseConfigLoader):
72 class MyLoader1(ArgParseConfigLoader):
72 def _add_arguments(self, aliases=None, flags=None):
73 def _add_arguments(self, aliases=None, flags=None):
73 p = self.parser
74 p = self.parser
74 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
75 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
75 p.add_argument('-b', dest='MyClass.bar', type=int)
76 p.add_argument('-b', dest='MyClass.bar', type=int)
76 p.add_argument('-n', dest='n', action='store_true')
77 p.add_argument('-n', dest='n', action='store_true')
77 p.add_argument('Global.bam', type=str)
78 p.add_argument('Global.bam', type=str)
78
79
79 class MyLoader2(ArgParseConfigLoader):
80 class MyLoader2(ArgParseConfigLoader):
80 def _add_arguments(self, aliases=None, flags=None):
81 def _add_arguments(self, aliases=None, flags=None):
81 subparsers = self.parser.add_subparsers(dest='subparser_name')
82 subparsers = self.parser.add_subparsers(dest='subparser_name')
82 subparser1 = subparsers.add_parser('1')
83 subparser1 = subparsers.add_parser('1')
83 subparser1.add_argument('-x',dest='Global.x')
84 subparser1.add_argument('-x',dest='Global.x')
84 subparser2 = subparsers.add_parser('2')
85 subparser2 = subparsers.add_parser('2')
85 subparser2.add_argument('y')
86 subparser2.add_argument('y')
86
87
87 class TestArgParseCL(TestCase):
88 class TestArgParseCL(TestCase):
88
89
89 def test_basic(self):
90 def test_basic(self):
90 cl = MyLoader1()
91 cl = MyLoader1()
91 config = cl.load_config('-f hi -b 10 -n wow'.split())
92 config = cl.load_config('-f hi -b 10 -n wow'.split())
92 self.assertEquals(config.Global.foo, 'hi')
93 self.assertEquals(config.Global.foo, 'hi')
93 self.assertEquals(config.MyClass.bar, 10)
94 self.assertEquals(config.MyClass.bar, 10)
94 self.assertEquals(config.n, True)
95 self.assertEquals(config.n, True)
95 self.assertEquals(config.Global.bam, 'wow')
96 self.assertEquals(config.Global.bam, 'wow')
96 config = cl.load_config(['wow'])
97 config = cl.load_config(['wow'])
97 self.assertEquals(config.keys(), ['Global'])
98 self.assertEquals(config.keys(), ['Global'])
98 self.assertEquals(config.Global.keys(), ['bam'])
99 self.assertEquals(config.Global.keys(), ['bam'])
99 self.assertEquals(config.Global.bam, 'wow')
100 self.assertEquals(config.Global.bam, 'wow')
100
101
101 def test_add_arguments(self):
102 def test_add_arguments(self):
102 cl = MyLoader2()
103 cl = MyLoader2()
103 config = cl.load_config('2 frobble'.split())
104 config = cl.load_config('2 frobble'.split())
104 self.assertEquals(config.subparser_name, '2')
105 self.assertEquals(config.subparser_name, '2')
105 self.assertEquals(config.y, 'frobble')
106 self.assertEquals(config.y, 'frobble')
106 config = cl.load_config('1 -x frobble'.split())
107 config = cl.load_config('1 -x frobble'.split())
107 self.assertEquals(config.subparser_name, '1')
108 self.assertEquals(config.subparser_name, '1')
108 self.assertEquals(config.Global.x, 'frobble')
109 self.assertEquals(config.Global.x, 'frobble')
109
110
110 def test_argv(self):
111 def test_argv(self):
111 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
112 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
112 config = cl.load_config()
113 config = cl.load_config()
113 self.assertEquals(config.Global.foo, 'hi')
114 self.assertEquals(config.Global.foo, 'hi')
114 self.assertEquals(config.MyClass.bar, 10)
115 self.assertEquals(config.MyClass.bar, 10)
115 self.assertEquals(config.n, True)
116 self.assertEquals(config.n, True)
116 self.assertEquals(config.Global.bam, 'wow')
117 self.assertEquals(config.Global.bam, 'wow')
117
118
118
119
119 class TestKeyValueCL(TestCase):
120 class TestKeyValueCL(TestCase):
121 klass = KeyValueConfigLoader
120
122
121 def test_basic(self):
123 def test_basic(self):
122 cl = KeyValueConfigLoader()
124 cl = self.klass()
123 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]]
124 with mute_warn():
126 with mute_warn():
125 config = cl.load_config(argv)
127 config = cl.load_config(argv)
126 self.assertEquals(config.a, 10)
128 self.assertEquals(config.a, 10)
127 self.assertEquals(config.b, 20)
129 self.assertEquals(config.b, 20)
128 self.assertEquals(config.Foo.Bar.value, 10)
130 self.assertEquals(config.Foo.Bar.value, 10)
129 self.assertEquals(config.Foo.Bam.value, range(10))
131 self.assertEquals(config.Foo.Bam.value, range(10))
130 self.assertEquals(config.D.C.value, 'hi there')
132 self.assertEquals(config.D.C.value, 'hi there')
131
133
132 def test_extra_args(self):
134 def test_extra_args(self):
133 cl = KeyValueConfigLoader()
135 cl = self.klass()
134 with mute_warn():
136 with mute_warn():
135 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
137 config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
136 self.assertEquals(cl.extra_args, ['b', 'd'])
138 self.assertEquals(cl.extra_args, ['b', 'd'])
137 self.assertEquals(config.a, 5)
139 self.assertEquals(config.a, 5)
138 self.assertEquals(config.c, 10)
140 self.assertEquals(config.c, 10)
139 with mute_warn():
141 with mute_warn():
140 config = cl.load_config(['--', '--a=5', '--c=10'])
142 config = cl.load_config(['--', '--a=5', '--c=10'])
141 self.assertEquals(cl.extra_args, ['--a=5', '--c=10'])
143 self.assertEquals(cl.extra_args, ['--a=5', '--c=10'])
142
144
143 def test_unicode_args(self):
145 def test_unicode_args(self):
144 cl = KeyValueConfigLoader()
146 cl = self.klass()
145 argv = [u'--a=épsîlön']
147 argv = [u'--a=épsîlön']
146 with mute_warn():
148 with mute_warn():
147 config = cl.load_config(argv)
149 config = cl.load_config(argv)
148 self.assertEquals(config.a, u'épsîlön')
150 self.assertEquals(config.a, u'épsîlön')
149
151
150 def test_unicode_bytes_args(self):
152 def test_unicode_bytes_args(self):
151 uarg = u'--a=é'
153 uarg = u'--a=é'
152 try:
154 try:
153 barg = uarg.encode(sys.stdin.encoding)
155 barg = uarg.encode(sys.stdin.encoding)
154 except (TypeError, UnicodeEncodeError):
156 except (TypeError, UnicodeEncodeError):
155 raise SkipTest("sys.stdin.encoding can't handle 'é'")
157 raise SkipTest("sys.stdin.encoding can't handle 'é'")
156
158
157 cl = KeyValueConfigLoader()
159 cl = self.klass()
158 with mute_warn():
160 with mute_warn():
159 config = cl.load_config([barg])
161 config = cl.load_config([barg])
160 self.assertEquals(config.a, u'é')
162 self.assertEquals(config.a, u'é')
163
164 def test_unicode_alias(self):
165 cl = self.klass()
166 argv = [u'--a=épsîlön']
167 with mute_warn():
168 config = cl.load_config(argv, aliases=dict(a='A.a'))
169 self.assertEquals(config.A.a, u'épsîlön')
170
161
171
172 class TestArgParseKVCL(TestKeyValueCL):
173 klass = KVArgParseConfigLoader
162
174
163 class TestConfig(TestCase):
175 class TestConfig(TestCase):
164
176
165 def test_setget(self):
177 def test_setget(self):
166 c = Config()
178 c = Config()
167 c.a = 10
179 c.a = 10
168 self.assertEquals(c.a, 10)
180 self.assertEquals(c.a, 10)
169 self.assertEquals(c.has_key('b'), False)
181 self.assertEquals(c.has_key('b'), False)
170
182
171 def test_auto_section(self):
183 def test_auto_section(self):
172 c = Config()
184 c = Config()
173 self.assertEquals(c.has_key('A'), True)
185 self.assertEquals(c.has_key('A'), True)
174 self.assertEquals(c._has_section('A'), False)
186 self.assertEquals(c._has_section('A'), False)
175 A = c.A
187 A = c.A
176 A.foo = 'hi there'
188 A.foo = 'hi there'
177 self.assertEquals(c._has_section('A'), True)
189 self.assertEquals(c._has_section('A'), True)
178 self.assertEquals(c.A.foo, 'hi there')
190 self.assertEquals(c.A.foo, 'hi there')
179 del c.A
191 del c.A
180 self.assertEquals(len(c.A.keys()),0)
192 self.assertEquals(len(c.A.keys()),0)
181
193
182 def test_merge_doesnt_exist(self):
194 def test_merge_doesnt_exist(self):
183 c1 = Config()
195 c1 = Config()
184 c2 = Config()
196 c2 = Config()
185 c2.bar = 10
197 c2.bar = 10
186 c2.Foo.bar = 10
198 c2.Foo.bar = 10
187 c1._merge(c2)
199 c1._merge(c2)
188 self.assertEquals(c1.Foo.bar, 10)
200 self.assertEquals(c1.Foo.bar, 10)
189 self.assertEquals(c1.bar, 10)
201 self.assertEquals(c1.bar, 10)
190 c2.Bar.bar = 10
202 c2.Bar.bar = 10
191 c1._merge(c2)
203 c1._merge(c2)
192 self.assertEquals(c1.Bar.bar, 10)
204 self.assertEquals(c1.Bar.bar, 10)
193
205
194 def test_merge_exists(self):
206 def test_merge_exists(self):
195 c1 = Config()
207 c1 = Config()
196 c2 = Config()
208 c2 = Config()
197 c1.Foo.bar = 10
209 c1.Foo.bar = 10
198 c1.Foo.bam = 30
210 c1.Foo.bam = 30
199 c2.Foo.bar = 20
211 c2.Foo.bar = 20
200 c2.Foo.wow = 40
212 c2.Foo.wow = 40
201 c1._merge(c2)
213 c1._merge(c2)
202 self.assertEquals(c1.Foo.bam, 30)
214 self.assertEquals(c1.Foo.bam, 30)
203 self.assertEquals(c1.Foo.bar, 20)
215 self.assertEquals(c1.Foo.bar, 20)
204 self.assertEquals(c1.Foo.wow, 40)
216 self.assertEquals(c1.Foo.wow, 40)
205 c2.Foo.Bam.bam = 10
217 c2.Foo.Bam.bam = 10
206 c1._merge(c2)
218 c1._merge(c2)
207 self.assertEquals(c1.Foo.Bam.bam, 10)
219 self.assertEquals(c1.Foo.Bam.bam, 10)
208
220
209 def test_deepcopy(self):
221 def test_deepcopy(self):
210 c1 = Config()
222 c1 = Config()
211 c1.Foo.bar = 10
223 c1.Foo.bar = 10
212 c1.Foo.bam = 30
224 c1.Foo.bam = 30
213 c1.a = 'asdf'
225 c1.a = 'asdf'
214 c1.b = range(10)
226 c1.b = range(10)
215 import copy
227 import copy
216 c2 = copy.deepcopy(c1)
228 c2 = copy.deepcopy(c1)
217 self.assertEquals(c1, c2)
229 self.assertEquals(c1, c2)
218 self.assert_(c1 is not c2)
230 self.assert_(c1 is not c2)
219 self.assert_(c1.Foo is not c2.Foo)
231 self.assert_(c1.Foo is not c2.Foo)
220
232
221 def test_builtin(self):
233 def test_builtin(self):
222 c1 = Config()
234 c1 = Config()
223 exec 'foo = True' in c1
235 exec 'foo = True' in c1
224 self.assertEquals(c1.foo, True)
236 self.assertEquals(c1.foo, True)
225 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
237 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
General Comments 0
You need to be logged in to leave comments. Login now