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