##// END OF EJS Templates
Added KeyValueConfigLoader with tests.
Brian Granger -
Show More
@@ -1,389 +1,451 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # coding: utf-8
2 # coding: utf-8
3 """A simple configuration system.
3 """A simple configuration system.
4
4
5 Authors
5 Authors
6 -------
6 -------
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
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 __builtin__
22 import __builtin__
23 import os
23 import os
24 import sys
24 import sys
25
25
26 from IPython.external import argparse
26 from IPython.external import argparse
27 from IPython.utils.path import filefind
27 from IPython.utils.path import filefind
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 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Argparse fix
42 # Argparse fix
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 # Unfortunately argparse by default prints help messages to stderr instead of
45 # Unfortunately argparse by default prints help messages to stderr instead of
46 # stdout. This makes it annoying to capture long help screens at the command
46 # stdout. This makes it annoying to capture long help screens at the command
47 # line, since one must know how to pipe stderr, which many users don't know how
47 # line, since one must know how to pipe stderr, which many users don't know how
48 # to do. So we override the print_help method with one that defaults to
48 # to do. So we override the print_help method with one that defaults to
49 # stdout and use our class instead.
49 # stdout and use our class instead.
50
50
51 class ArgumentParser(argparse.ArgumentParser):
51 class ArgumentParser(argparse.ArgumentParser):
52 """Simple argparse subclass that prints help to stdout by default."""
52 """Simple argparse subclass that prints help to stdout by default."""
53
53
54 def print_help(self, file=None):
54 def print_help(self, file=None):
55 if file is None:
55 if file is None:
56 file = sys.stdout
56 file = sys.stdout
57 return super(ArgumentParser, self).print_help(file)
57 return super(ArgumentParser, self).print_help(file)
58
58
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Config class for holding config information
62 # Config class for holding config information
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 class Config(dict):
66 class Config(dict):
67 """An attribute based dict that can do smart merges."""
67 """An attribute based dict that can do smart merges."""
68
68
69 def __init__(self, *args, **kwds):
69 def __init__(self, *args, **kwds):
70 dict.__init__(self, *args, **kwds)
70 dict.__init__(self, *args, **kwds)
71 # This sets self.__dict__ = self, but it has to be done this way
71 # This sets self.__dict__ = self, but it has to be done this way
72 # because we are also overriding __setattr__.
72 # because we are also overriding __setattr__.
73 dict.__setattr__(self, '__dict__', self)
73 dict.__setattr__(self, '__dict__', self)
74
74
75 def _merge(self, other):
75 def _merge(self, other):
76 to_update = {}
76 to_update = {}
77 for k, v in other.iteritems():
77 for k, v in other.iteritems():
78 if not self.has_key(k):
78 if not self.has_key(k):
79 to_update[k] = v
79 to_update[k] = v
80 else: # I have this key
80 else: # I have this key
81 if isinstance(v, Config):
81 if isinstance(v, Config):
82 # Recursively merge common sub Configs
82 # Recursively merge common sub Configs
83 self[k]._merge(v)
83 self[k]._merge(v)
84 else:
84 else:
85 # Plain updates for non-Configs
85 # Plain updates for non-Configs
86 to_update[k] = v
86 to_update[k] = v
87
87
88 self.update(to_update)
88 self.update(to_update)
89
89
90 def _is_section_key(self, key):
90 def _is_section_key(self, key):
91 if key[0].upper()==key[0] and not key.startswith('_'):
91 if key[0].upper()==key[0] and not key.startswith('_'):
92 return True
92 return True
93 else:
93 else:
94 return False
94 return False
95
95
96 def __contains__(self, key):
96 def __contains__(self, key):
97 if self._is_section_key(key):
97 if self._is_section_key(key):
98 return True
98 return True
99 else:
99 else:
100 return super(Config, self).__contains__(key)
100 return super(Config, self).__contains__(key)
101 # .has_key is deprecated for dictionaries.
101 # .has_key is deprecated for dictionaries.
102 has_key = __contains__
102 has_key = __contains__
103
103
104 def _has_section(self, key):
104 def _has_section(self, key):
105 if self._is_section_key(key):
105 if self._is_section_key(key):
106 if super(Config, self).__contains__(key):
106 if super(Config, self).__contains__(key):
107 return True
107 return True
108 return False
108 return False
109
109
110 def copy(self):
110 def copy(self):
111 return type(self)(dict.copy(self))
111 return type(self)(dict.copy(self))
112
112
113 def __copy__(self):
113 def __copy__(self):
114 return self.copy()
114 return self.copy()
115
115
116 def __deepcopy__(self, memo):
116 def __deepcopy__(self, memo):
117 import copy
117 import copy
118 return type(self)(copy.deepcopy(self.items()))
118 return type(self)(copy.deepcopy(self.items()))
119
119
120 def __getitem__(self, key):
120 def __getitem__(self, key):
121 # We cannot use directly self._is_section_key, because it triggers
121 # We cannot use directly self._is_section_key, because it triggers
122 # infinite recursion on top of PyPy. Instead, we manually fish the
122 # infinite recursion on top of PyPy. Instead, we manually fish the
123 # bound method.
123 # bound method.
124 is_section_key = self.__class__._is_section_key.__get__(self)
124 is_section_key = self.__class__._is_section_key.__get__(self)
125
125
126 # Because we use this for an exec namespace, we need to delegate
126 # Because we use this for an exec namespace, we need to delegate
127 # the lookup of names in __builtin__ to itself. This means
127 # the lookup of names in __builtin__ to itself. This means
128 # that you can't have section or attribute names that are
128 # that you can't have section or attribute names that are
129 # builtins.
129 # builtins.
130 try:
130 try:
131 return getattr(__builtin__, key)
131 return getattr(__builtin__, key)
132 except AttributeError:
132 except AttributeError:
133 pass
133 pass
134 if is_section_key(key):
134 if is_section_key(key):
135 try:
135 try:
136 return dict.__getitem__(self, key)
136 return dict.__getitem__(self, key)
137 except KeyError:
137 except KeyError:
138 c = Config()
138 c = Config()
139 dict.__setitem__(self, key, c)
139 dict.__setitem__(self, key, c)
140 return c
140 return c
141 else:
141 else:
142 return dict.__getitem__(self, key)
142 return dict.__getitem__(self, key)
143
143
144 def __setitem__(self, key, value):
144 def __setitem__(self, key, value):
145 # Don't allow names in __builtin__ to be modified.
145 # Don't allow names in __builtin__ to be modified.
146 if hasattr(__builtin__, key):
146 if hasattr(__builtin__, key):
147 raise ConfigError('Config variable names cannot have the same name '
147 raise ConfigError('Config variable names cannot have the same name '
148 'as a Python builtin: %s' % key)
148 'as a Python builtin: %s' % key)
149 if self._is_section_key(key):
149 if self._is_section_key(key):
150 if not isinstance(value, Config):
150 if not isinstance(value, Config):
151 raise ValueError('values whose keys begin with an uppercase '
151 raise ValueError('values whose keys begin with an uppercase '
152 'char must be Config instances: %r, %r' % (key, value))
152 'char must be Config instances: %r, %r' % (key, value))
153 else:
153 else:
154 dict.__setitem__(self, key, value)
154 dict.__setitem__(self, key, value)
155
155
156 def __getattr__(self, key):
156 def __getattr__(self, key):
157 try:
157 try:
158 return self.__getitem__(key)
158 return self.__getitem__(key)
159 except KeyError, e:
159 except KeyError, e:
160 raise AttributeError(e)
160 raise AttributeError(e)
161
161
162 def __setattr__(self, key, value):
162 def __setattr__(self, key, value):
163 try:
163 try:
164 self.__setitem__(key, value)
164 self.__setitem__(key, value)
165 except KeyError, e:
165 except KeyError, e:
166 raise AttributeError(e)
166 raise AttributeError(e)
167
167
168 def __delattr__(self, key):
168 def __delattr__(self, key):
169 try:
169 try:
170 dict.__delitem__(self, key)
170 dict.__delitem__(self, key)
171 except KeyError, e:
171 except KeyError, e:
172 raise AttributeError(e)
172 raise AttributeError(e)
173
173
174
174
175 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
176 # Config loading classes
176 # Config loading classes
177 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
178
178
179
179
180 class ConfigLoader(object):
180 class ConfigLoader(object):
181 """A object for loading configurations from just about anywhere.
181 """A object for loading configurations from just about anywhere.
182
182
183 The resulting configuration is packaged as a :class:`Struct`.
183 The resulting configuration is packaged as a :class:`Struct`.
184
184
185 Notes
185 Notes
186 -----
186 -----
187 A :class:`ConfigLoader` does one thing: load a config from a source
187 A :class:`ConfigLoader` does one thing: load a config from a source
188 (file, command line arguments) and returns the data as a :class:`Struct`.
188 (file, command line arguments) and returns the data as a :class:`Struct`.
189 There are lots of things that :class:`ConfigLoader` does not do. It does
189 There are lots of things that :class:`ConfigLoader` does not do. It does
190 not implement complex logic for finding config files. It does not handle
190 not implement complex logic for finding config files. It does not handle
191 default values or merge multiple configs. These things need to be
191 default values or merge multiple configs. These things need to be
192 handled elsewhere.
192 handled elsewhere.
193 """
193 """
194
194
195 def __init__(self):
195 def __init__(self):
196 """A base class for config loaders.
196 """A base class for config loaders.
197
197
198 Examples
198 Examples
199 --------
199 --------
200
200
201 >>> cl = ConfigLoader()
201 >>> cl = ConfigLoader()
202 >>> config = cl.load_config()
202 >>> config = cl.load_config()
203 >>> config
203 >>> config
204 {}
204 {}
205 """
205 """
206 self.clear()
206 self.clear()
207
207
208 def clear(self):
208 def clear(self):
209 self.config = Config()
209 self.config = Config()
210
210
211 def load_config(self):
211 def load_config(self):
212 """Load a config from somewhere, return a :class:`Config` instance.
212 """Load a config from somewhere, return a :class:`Config` instance.
213
213
214 Usually, this will cause self.config to be set and then returned.
214 Usually, this will cause self.config to be set and then returned.
215 However, in most cases, :meth:`ConfigLoader.clear` should be called
215 However, in most cases, :meth:`ConfigLoader.clear` should be called
216 to erase any previous state.
216 to erase any previous state.
217 """
217 """
218 self.clear()
218 self.clear()
219 return self.config
219 return self.config
220
220
221
221
222 class FileConfigLoader(ConfigLoader):
222 class FileConfigLoader(ConfigLoader):
223 """A base class for file based configurations.
223 """A base class for file based configurations.
224
224
225 As we add more file based config loaders, the common logic should go
225 As we add more file based config loaders, the common logic should go
226 here.
226 here.
227 """
227 """
228 pass
228 pass
229
229
230
230
231 class PyFileConfigLoader(FileConfigLoader):
231 class PyFileConfigLoader(FileConfigLoader):
232 """A config loader for pure python files.
232 """A config loader for pure python files.
233
233
234 This calls execfile on a plain python file and looks for attributes
234 This calls execfile on a plain python file and looks for attributes
235 that are all caps. These attribute are added to the config Struct.
235 that are all caps. These attribute are added to the config Struct.
236 """
236 """
237
237
238 def __init__(self, filename, path=None):
238 def __init__(self, filename, path=None):
239 """Build a config loader for a filename and path.
239 """Build a config loader for a filename and path.
240
240
241 Parameters
241 Parameters
242 ----------
242 ----------
243 filename : str
243 filename : str
244 The file name of the config file.
244 The file name of the config file.
245 path : str, list, tuple
245 path : str, list, tuple
246 The path to search for the config file on, or a sequence of
246 The path to search for the config file on, or a sequence of
247 paths to try in order.
247 paths to try in order.
248 """
248 """
249 super(PyFileConfigLoader, self).__init__()
249 super(PyFileConfigLoader, self).__init__()
250 self.filename = filename
250 self.filename = filename
251 self.path = path
251 self.path = path
252 self.full_filename = ''
252 self.full_filename = ''
253 self.data = None
253 self.data = None
254
254
255 def load_config(self):
255 def load_config(self):
256 """Load the config from a file and return it as a Struct."""
256 """Load the config from a file and return it as a Struct."""
257 self.clear()
257 self.clear()
258 self._find_file()
258 self._find_file()
259 self._read_file_as_dict()
259 self._read_file_as_dict()
260 self._convert_to_config()
260 self._convert_to_config()
261 return self.config
261 return self.config
262
262
263 def _find_file(self):
263 def _find_file(self):
264 """Try to find the file by searching the paths."""
264 """Try to find the file by searching the paths."""
265 self.full_filename = filefind(self.filename, self.path)
265 self.full_filename = filefind(self.filename, self.path)
266
266
267 def _read_file_as_dict(self):
267 def _read_file_as_dict(self):
268 """Load the config file into self.config, with recursive loading."""
268 """Load the config file into self.config, with recursive loading."""
269 # This closure is made available in the namespace that is used
269 # This closure is made available in the namespace that is used
270 # to exec the config file. This allows users to call
270 # to exec the config file. This allows users to call
271 # load_subconfig('myconfig.py') to load config files recursively.
271 # load_subconfig('myconfig.py') to load config files recursively.
272 # It needs to be a closure because it has references to self.path
272 # It needs to be a closure because it has references to self.path
273 # and self.config. The sub-config is loaded with the same path
273 # and self.config. The sub-config is loaded with the same path
274 # as the parent, but it uses an empty config which is then merged
274 # as the parent, but it uses an empty config which is then merged
275 # with the parents.
275 # with the parents.
276 def load_subconfig(fname):
276 def load_subconfig(fname):
277 loader = PyFileConfigLoader(fname, self.path)
277 loader = PyFileConfigLoader(fname, self.path)
278 try:
278 try:
279 sub_config = loader.load_config()
279 sub_config = loader.load_config()
280 except IOError:
280 except IOError:
281 # Pass silently if the sub config is not there. This happens
281 # Pass silently if the sub config is not there. This happens
282 # when a user us using a profile, but not the default config.
282 # when a user us using a profile, but not the default config.
283 pass
283 pass
284 else:
284 else:
285 self.config._merge(sub_config)
285 self.config._merge(sub_config)
286
286
287 # Again, this needs to be a closure and should be used in config
287 # Again, this needs to be a closure and should be used in config
288 # files to get the config being loaded.
288 # files to get the config being loaded.
289 def get_config():
289 def get_config():
290 return self.config
290 return self.config
291
291
292 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
292 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
293 fs_encoding = sys.getfilesystemencoding() or 'ascii'
293 fs_encoding = sys.getfilesystemencoding() or 'ascii'
294 conf_filename = self.full_filename.encode(fs_encoding)
294 conf_filename = self.full_filename.encode(fs_encoding)
295 execfile(conf_filename, namespace)
295 execfile(conf_filename, namespace)
296
296
297 def _convert_to_config(self):
297 def _convert_to_config(self):
298 if self.data is None:
298 if self.data is None:
299 ConfigLoaderError('self.data does not exist')
299 ConfigLoaderError('self.data does not exist')
300
300
301
301
302 class CommandLineConfigLoader(ConfigLoader):
302 class CommandLineConfigLoader(ConfigLoader):
303 """A config loader for command line arguments.
303 """A config loader for command line arguments.
304
304
305 As we add more command line based loaders, the common logic should go
305 As we add more command line based loaders, the common logic should go
306 here.
306 here.
307 """
307 """
308
308
309
309
310 class KeyValueConfigLoader(CommandLineConfigLoader):
311 """A config loader that loads key value pairs from the command line.
312
313 This allows command line options to be gives in the following form::
314
315 ipython Global.profile="foo" InteractiveShell.autocall=False
316 """
317
318 def __init__(self, argv=None):
319 """Create a key value pair config loader.
320
321 Parameters
322 ----------
323 argv : list
324 A list that has the form of sys.argv[1:] which has unicode
325 elements of the form u"key=value". If this is None (default),
326 then sys.argv[1:] will be used.
327
328 Returns
329 -------
330 config : Config
331 The resulting Config object.
332
333 Examples
334 --------
335
336 >>> from IPython.config.loader import KeyValueConfigLoader
337 >>> cl = KeyValueConfigLoader()
338 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
339 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
340 """
341 if argv == None:
342 argv = sys.argv[1:]
343 self.argv = argv
344
345 def load_config(self, argv=None):
346 """Parse the configuration and generate the Config object.
347
348 Parameters
349 ----------
350 argv : list, optional
351 A list that has the form of sys.argv[1:] which has unicode
352 elements of the form u"key=value". If this is None (default),
353 then self.argv will be used.
354 """
355 self.clear()
356 if argv is None:
357 argv = self.argv
358 for item in argv:
359 pair = tuple(item.split("="))
360 if len(pair) == 2:
361 exec_str = 'self.config.' + pair[0] + '=' + pair[1]
362 exec exec_str in locals(), globals()
363 return self.config
364
365
310 class ArgParseConfigLoader(CommandLineConfigLoader):
366 class ArgParseConfigLoader(CommandLineConfigLoader):
367 """A loader that uses the argparse module to load from the command line."""
311
368
312 def __init__(self, argv=None, *parser_args, **parser_kw):
369 def __init__(self, argv=None, *parser_args, **parser_kw):
313 """Create a config loader for use with argparse.
370 """Create a config loader for use with argparse.
314
371
315 Parameters
372 Parameters
316 ----------
373 ----------
317
374
318 argv : optional, list
375 argv : optional, list
319 If given, used to read command-line arguments from, otherwise
376 If given, used to read command-line arguments from, otherwise
320 sys.argv[1:] is used.
377 sys.argv[1:] is used.
321
378
322 parser_args : tuple
379 parser_args : tuple
323 A tuple of positional arguments that will be passed to the
380 A tuple of positional arguments that will be passed to the
324 constructor of :class:`argparse.ArgumentParser`.
381 constructor of :class:`argparse.ArgumentParser`.
325
382
326 parser_kw : dict
383 parser_kw : dict
327 A tuple of keyword arguments that will be passed to the
384 A tuple of keyword arguments that will be passed to the
328 constructor of :class:`argparse.ArgumentParser`.
385 constructor of :class:`argparse.ArgumentParser`.
386
387 Returns
388 -------
389 config : Config
390 The resulting Config object.
329 """
391 """
330 super(CommandLineConfigLoader, self).__init__()
392 super(CommandLineConfigLoader, self).__init__()
331 if argv == None:
393 if argv == None:
332 argv = sys.argv[1:]
394 argv = sys.argv[1:]
333 self.argv = argv
395 self.argv = argv
334 self.parser_args = parser_args
396 self.parser_args = parser_args
335 self.version = parser_kw.pop("version", None)
397 self.version = parser_kw.pop("version", None)
336 kwargs = dict(argument_default=argparse.SUPPRESS)
398 kwargs = dict(argument_default=argparse.SUPPRESS)
337 kwargs.update(parser_kw)
399 kwargs.update(parser_kw)
338 self.parser_kw = kwargs
400 self.parser_kw = kwargs
339
401
340 def load_config(self, args=None):
402 def load_config(self, argv=None):
341 """Parse command line arguments and return as a Struct.
403 """Parse command line arguments and return as a Config object.
342
404
343 Parameters
405 Parameters
344 ----------
406 ----------
345
407
346 args : optional, list
408 args : optional, list
347 If given, a list with the structure of sys.argv[1:] to parse
409 If given, a list with the structure of sys.argv[1:] to parse
348 arguments from. If not given, the instance's self.argv attribute
410 arguments from. If not given, the instance's self.argv attribute
349 (given at construction time) is used."""
411 (given at construction time) is used."""
350 self.clear()
412 self.clear()
351 if args is None:
413 if argv is None:
352 args = self.argv
414 argv = self.argv
353 self._create_parser()
415 self._create_parser()
354 self._parse_args(args)
416 self._parse_args(argv)
355 self._convert_to_config()
417 self._convert_to_config()
356 return self.config
418 return self.config
357
419
358 def get_extra_args(self):
420 def get_extra_args(self):
359 if hasattr(self, 'extra_args'):
421 if hasattr(self, 'extra_args'):
360 return self.extra_args
422 return self.extra_args
361 else:
423 else:
362 return []
424 return []
363
425
364 def _create_parser(self):
426 def _create_parser(self):
365 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
427 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
366 self._add_arguments()
428 self._add_arguments()
367
429
368 def _add_arguments(self):
430 def _add_arguments(self):
369 raise NotImplementedError("subclasses must implement _add_arguments")
431 raise NotImplementedError("subclasses must implement _add_arguments")
370
432
371 def _parse_args(self, args):
433 def _parse_args(self, args):
372 """self.parser->self.parsed_data"""
434 """self.parser->self.parsed_data"""
373 # decode sys.argv to support unicode command-line options
435 # decode sys.argv to support unicode command-line options
374 uargs = []
436 uargs = []
375 for a in args:
437 for a in args:
376 if isinstance(a, str):
438 if isinstance(a, str):
377 # don't decode if we already got unicode
439 # don't decode if we already got unicode
378 a = a.decode(sys.stdin.encoding or
440 a = a.decode(sys.stdin.encoding or
379 sys.getdefaultencoding())
441 sys.getdefaultencoding())
380 uargs.append(a)
442 uargs.append(a)
381 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
443 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
382
444
383 def _convert_to_config(self):
445 def _convert_to_config(self):
384 """self.parsed_data->self.config"""
446 """self.parsed_data->self.config"""
385 for k, v in vars(self.parsed_data).iteritems():
447 for k, v in vars(self.parsed_data).iteritems():
386 exec_str = 'self.config.' + k + '= v'
448 exec_str = 'self.config.' + k + '= v'
387 exec exec_str in locals(), globals()
449 exec exec_str in locals(), globals()
388
450
389
451
@@ -1,174 +1,188 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 from tempfile import mkstemp
24 from tempfile import mkstemp
25 from unittest import TestCase
25 from unittest import TestCase
26
26
27 from IPython.config.loader import (
27 from IPython.config.loader import (
28 Config,
28 Config,
29 PyFileConfigLoader,
29 PyFileConfigLoader,
30 KeyValueConfigLoader,
30 ArgParseConfigLoader,
31 ArgParseConfigLoader,
31 ConfigError
32 ConfigError
32 )
33 )
33
34
34 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
35 # Actual tests
36 # Actual tests
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37
38
38
39
39 pyfile = """
40 pyfile = """
40 c = get_config()
41 c = get_config()
41 c.a = 10
42 c.a=10
42 c.b = 20
43 c.b=20
43 c.Foo.Bar.value = 10
44 c.Foo.Bar.value=10
44 c.Foo.Bam.value = range(10)
45 c.Foo.Bam.value=range(10)
45 c.D.C.value = 'hi there'
46 c.D.C.value='hi there'
46 """
47 """
47
48
48 class TestPyFileCL(TestCase):
49 class TestPyFileCL(TestCase):
49
50
50 def test_basic(self):
51 def test_basic(self):
51 fd, fname = mkstemp('.py')
52 fd, fname = mkstemp('.py')
52 f = os.fdopen(fd, 'w')
53 f = os.fdopen(fd, 'w')
53 f.write(pyfile)
54 f.write(pyfile)
54 f.close()
55 f.close()
55 # Unlink the file
56 # Unlink the file
56 cl = PyFileConfigLoader(fname)
57 cl = PyFileConfigLoader(fname)
57 config = cl.load_config()
58 config = cl.load_config()
58 self.assertEquals(config.a, 10)
59 self.assertEquals(config.a, 10)
59 self.assertEquals(config.b, 20)
60 self.assertEquals(config.b, 20)
60 self.assertEquals(config.Foo.Bar.value, 10)
61 self.assertEquals(config.Foo.Bar.value, 10)
61 self.assertEquals(config.Foo.Bam.value, range(10))
62 self.assertEquals(config.Foo.Bam.value, range(10))
62 self.assertEquals(config.D.C.value, 'hi there')
63 self.assertEquals(config.D.C.value, 'hi there')
63
64
64 class MyLoader1(ArgParseConfigLoader):
65 class MyLoader1(ArgParseConfigLoader):
65 def _add_arguments(self):
66 def _add_arguments(self):
66 p = self.parser
67 p = self.parser
67 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
68 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
68 p.add_argument('-b', dest='MyClass.bar', type=int)
69 p.add_argument('-b', dest='MyClass.bar', type=int)
69 p.add_argument('-n', dest='n', action='store_true')
70 p.add_argument('-n', dest='n', action='store_true')
70 p.add_argument('Global.bam', type=str)
71 p.add_argument('Global.bam', type=str)
71
72
72 class MyLoader2(ArgParseConfigLoader):
73 class MyLoader2(ArgParseConfigLoader):
73 def _add_arguments(self):
74 def _add_arguments(self):
74 subparsers = self.parser.add_subparsers(dest='subparser_name')
75 subparsers = self.parser.add_subparsers(dest='subparser_name')
75 subparser1 = subparsers.add_parser('1')
76 subparser1 = subparsers.add_parser('1')
76 subparser1.add_argument('-x',dest='Global.x')
77 subparser1.add_argument('-x',dest='Global.x')
77 subparser2 = subparsers.add_parser('2')
78 subparser2 = subparsers.add_parser('2')
78 subparser2.add_argument('y')
79 subparser2.add_argument('y')
79
80
80 class TestArgParseCL(TestCase):
81 class TestArgParseCL(TestCase):
81
82
82 def test_basic(self):
83 def test_basic(self):
83 cl = MyLoader1()
84 cl = MyLoader1()
84 config = cl.load_config('-f hi -b 10 -n wow'.split())
85 config = cl.load_config('-f hi -b 10 -n wow'.split())
85 self.assertEquals(config.Global.foo, 'hi')
86 self.assertEquals(config.Global.foo, 'hi')
86 self.assertEquals(config.MyClass.bar, 10)
87 self.assertEquals(config.MyClass.bar, 10)
87 self.assertEquals(config.n, True)
88 self.assertEquals(config.n, True)
88 self.assertEquals(config.Global.bam, 'wow')
89 self.assertEquals(config.Global.bam, 'wow')
89 config = cl.load_config(['wow'])
90 config = cl.load_config(['wow'])
90 self.assertEquals(config.keys(), ['Global'])
91 self.assertEquals(config.keys(), ['Global'])
91 self.assertEquals(config.Global.keys(), ['bam'])
92 self.assertEquals(config.Global.keys(), ['bam'])
92 self.assertEquals(config.Global.bam, 'wow')
93 self.assertEquals(config.Global.bam, 'wow')
93
94
94 def test_add_arguments(self):
95 def test_add_arguments(self):
95 cl = MyLoader2()
96 cl = MyLoader2()
96 config = cl.load_config('2 frobble'.split())
97 config = cl.load_config('2 frobble'.split())
97 self.assertEquals(config.subparser_name, '2')
98 self.assertEquals(config.subparser_name, '2')
98 self.assertEquals(config.y, 'frobble')
99 self.assertEquals(config.y, 'frobble')
99 config = cl.load_config('1 -x frobble'.split())
100 config = cl.load_config('1 -x frobble'.split())
100 self.assertEquals(config.subparser_name, '1')
101 self.assertEquals(config.subparser_name, '1')
101 self.assertEquals(config.Global.x, 'frobble')
102 self.assertEquals(config.Global.x, 'frobble')
102
103
103 def test_argv(self):
104 def test_argv(self):
104 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
105 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
105 config = cl.load_config()
106 config = cl.load_config()
106 self.assertEquals(config.Global.foo, 'hi')
107 self.assertEquals(config.Global.foo, 'hi')
107 self.assertEquals(config.MyClass.bar, 10)
108 self.assertEquals(config.MyClass.bar, 10)
108 self.assertEquals(config.n, True)
109 self.assertEquals(config.n, True)
109 self.assertEquals(config.Global.bam, 'wow')
110 self.assertEquals(config.Global.bam, 'wow')
110
111
111
112
113 class TestKeyValueCL(TestCase):
114
115 def test_basic(self):
116 cl = KeyValueConfigLoader()
117 argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
118 print argv
119 config = cl.load_config(argv)
120 self.assertEquals(config.a, 10)
121 self.assertEquals(config.b, 20)
122 self.assertEquals(config.Foo.Bar.value, 10)
123 self.assertEquals(config.Foo.Bam.value, range(10))
124 self.assertEquals(config.D.C.value, 'hi there')
125
112 class TestConfig(TestCase):
126 class TestConfig(TestCase):
113
127
114 def test_setget(self):
128 def test_setget(self):
115 c = Config()
129 c = Config()
116 c.a = 10
130 c.a = 10
117 self.assertEquals(c.a, 10)
131 self.assertEquals(c.a, 10)
118 self.assertEquals(c.has_key('b'), False)
132 self.assertEquals(c.has_key('b'), False)
119
133
120 def test_auto_section(self):
134 def test_auto_section(self):
121 c = Config()
135 c = Config()
122 self.assertEquals(c.has_key('A'), True)
136 self.assertEquals(c.has_key('A'), True)
123 self.assertEquals(c._has_section('A'), False)
137 self.assertEquals(c._has_section('A'), False)
124 A = c.A
138 A = c.A
125 A.foo = 'hi there'
139 A.foo = 'hi there'
126 self.assertEquals(c._has_section('A'), True)
140 self.assertEquals(c._has_section('A'), True)
127 self.assertEquals(c.A.foo, 'hi there')
141 self.assertEquals(c.A.foo, 'hi there')
128 del c.A
142 del c.A
129 self.assertEquals(len(c.A.keys()),0)
143 self.assertEquals(len(c.A.keys()),0)
130
144
131 def test_merge_doesnt_exist(self):
145 def test_merge_doesnt_exist(self):
132 c1 = Config()
146 c1 = Config()
133 c2 = Config()
147 c2 = Config()
134 c2.bar = 10
148 c2.bar = 10
135 c2.Foo.bar = 10
149 c2.Foo.bar = 10
136 c1._merge(c2)
150 c1._merge(c2)
137 self.assertEquals(c1.Foo.bar, 10)
151 self.assertEquals(c1.Foo.bar, 10)
138 self.assertEquals(c1.bar, 10)
152 self.assertEquals(c1.bar, 10)
139 c2.Bar.bar = 10
153 c2.Bar.bar = 10
140 c1._merge(c2)
154 c1._merge(c2)
141 self.assertEquals(c1.Bar.bar, 10)
155 self.assertEquals(c1.Bar.bar, 10)
142
156
143 def test_merge_exists(self):
157 def test_merge_exists(self):
144 c1 = Config()
158 c1 = Config()
145 c2 = Config()
159 c2 = Config()
146 c1.Foo.bar = 10
160 c1.Foo.bar = 10
147 c1.Foo.bam = 30
161 c1.Foo.bam = 30
148 c2.Foo.bar = 20
162 c2.Foo.bar = 20
149 c2.Foo.wow = 40
163 c2.Foo.wow = 40
150 c1._merge(c2)
164 c1._merge(c2)
151 self.assertEquals(c1.Foo.bam, 30)
165 self.assertEquals(c1.Foo.bam, 30)
152 self.assertEquals(c1.Foo.bar, 20)
166 self.assertEquals(c1.Foo.bar, 20)
153 self.assertEquals(c1.Foo.wow, 40)
167 self.assertEquals(c1.Foo.wow, 40)
154 c2.Foo.Bam.bam = 10
168 c2.Foo.Bam.bam = 10
155 c1._merge(c2)
169 c1._merge(c2)
156 self.assertEquals(c1.Foo.Bam.bam, 10)
170 self.assertEquals(c1.Foo.Bam.bam, 10)
157
171
158 def test_deepcopy(self):
172 def test_deepcopy(self):
159 c1 = Config()
173 c1 = Config()
160 c1.Foo.bar = 10
174 c1.Foo.bar = 10
161 c1.Foo.bam = 30
175 c1.Foo.bam = 30
162 c1.a = 'asdf'
176 c1.a = 'asdf'
163 c1.b = range(10)
177 c1.b = range(10)
164 import copy
178 import copy
165 c2 = copy.deepcopy(c1)
179 c2 = copy.deepcopy(c1)
166 self.assertEquals(c1, c2)
180 self.assertEquals(c1, c2)
167 self.assert_(c1 is not c2)
181 self.assert_(c1 is not c2)
168 self.assert_(c1.Foo is not c2.Foo)
182 self.assert_(c1.Foo is not c2.Foo)
169
183
170 def test_builtin(self):
184 def test_builtin(self):
171 c1 = Config()
185 c1 = Config()
172 exec 'foo = True' in c1
186 exec 'foo = True' in c1
173 self.assertEquals(c1.foo, True)
187 self.assertEquals(c1.foo, True)
174 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
188 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
General Comments 0
You need to be logged in to leave comments. Login now