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