##// END OF EJS Templates
allow extra_args in applications
MinRK -
Show More
@@ -1,360 +1,359 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
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 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import re
23 import re
24 import sys
24 import sys
25
25
26 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.configurable import SingletonConfigurable
27 from IPython.config.loader import (
27 from IPython.config.loader import (
28 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
28 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
29 )
29 )
30
30
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 Unicode, List, Int, Enum, Dict, Instance
32 Unicode, List, Int, Enum, Dict, Instance
33 )
33 )
34 from IPython.utils.importstring import import_item
34 from IPython.utils.importstring import import_item
35 from IPython.utils.text import indent
35 from IPython.utils.text import indent
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Descriptions for the various sections
38 # Descriptions for the various sections
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 flag_description = """
41 flag_description = """
42 Flags are command-line arguments passed as '--<flag>'.
42 Flags are command-line arguments passed as '--<flag>'.
43 These take no parameters, unlike regular key-value arguments.
43 These take no parameters, unlike regular key-value arguments.
44 They are typically used for setting boolean flags, or enabling
44 They are typically used for setting boolean flags, or enabling
45 modes that involve setting multiple options together.
45 modes that involve setting multiple options together.
46 """.strip() # trim newlines of front and back
46 """.strip() # trim newlines of front and back
47
47
48 alias_description = """
48 alias_description = """
49 These are commonly set parameters, given abbreviated aliases for convenience.
49 These are commonly set parameters, given abbreviated aliases for convenience.
50 They are set in the same `name=value` way as class parameters, where
50 They are set in the same `name=value` way as class parameters, where
51 <name> is replaced by the real parameter for which it is an alias.
51 <name> is replaced by the real parameter for which it is an alias.
52 """.strip() # trim newlines of front and back
52 """.strip() # trim newlines of front and back
53
53
54 keyvalue_description = """
54 keyvalue_description = """
55 Parameters are set from command-line arguments of the form:
55 Parameters are set from command-line arguments of the form:
56 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
56 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
57 This line is evaluated in Python, so simple expressions are allowed, e.g.
57 This line is evaluated in Python, so simple expressions are allowed, e.g.
58 `C.a='range(3)'` For setting C.a=[0,1,2]
58 `C.a='range(3)'` For setting C.a=[0,1,2]
59 """.strip() # trim newlines of front and back
59 """.strip() # trim newlines of front and back
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Application class
62 # Application class
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 class ApplicationError(Exception):
66 class ApplicationError(Exception):
67 pass
67 pass
68
68
69
69
70 class Application(SingletonConfigurable):
70 class Application(SingletonConfigurable):
71 """A singleton application with full configuration support."""
71 """A singleton application with full configuration support."""
72
72
73 # The name of the application, will usually match the name of the command
73 # The name of the application, will usually match the name of the command
74 # line application
74 # line application
75 name = Unicode(u'application')
75 name = Unicode(u'application')
76
76
77 # The description of the application that is printed at the beginning
77 # The description of the application that is printed at the beginning
78 # of the help.
78 # of the help.
79 description = Unicode(u'This is an application.')
79 description = Unicode(u'This is an application.')
80 # default section descriptions
80 # default section descriptions
81 flag_description = Unicode(flag_description)
81 flag_description = Unicode(flag_description)
82 alias_description = Unicode(alias_description)
82 alias_description = Unicode(alias_description)
83 keyvalue_description = Unicode(keyvalue_description)
83 keyvalue_description = Unicode(keyvalue_description)
84
84
85
85
86 # A sequence of Configurable subclasses whose config=True attributes will
86 # A sequence of Configurable subclasses whose config=True attributes will
87 # be exposed at the command line.
87 # be exposed at the command line.
88 classes = List([])
88 classes = List([])
89
89
90 # The version string of this application.
90 # The version string of this application.
91 version = Unicode(u'0.0')
91 version = Unicode(u'0.0')
92
92
93 # The log level for the application
93 # The log level for the application
94 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
94 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
95 config=True,
95 config=True,
96 help="Set the log level.")
96 help="Set the log level.")
97
97
98 # the alias map for configurables
98 # the alias map for configurables
99 aliases = Dict(dict(log_level='Application.log_level'))
99 aliases = Dict(dict(log_level='Application.log_level'))
100
100
101 # flags for loading Configurables or store_const style flags
101 # flags for loading Configurables or store_const style flags
102 # flags are loaded from this dict by '--key' flags
102 # flags are loaded from this dict by '--key' flags
103 # this must be a dict of two-tuples, the first element being the Config/dict
103 # this must be a dict of two-tuples, the first element being the Config/dict
104 # and the second being the help string for the flag
104 # and the second being the help string for the flag
105 flags = Dict()
105 flags = Dict()
106
106
107 # subcommands for launching other applications
107 # subcommands for launching other applications
108 # if this is not empty, this will be a parent Application
108 # if this is not empty, this will be a parent Application
109 # this must be a dict of two-tuples, the first element being the application class/import string
109 # this must be a dict of two-tuples, the first element being the application class/import string
110 # and the second being the help string for the subcommand
110 # and the second being the help string for the subcommand
111 subcommands = Dict()
111 subcommands = Dict()
112 # parse_command_line will initialize a subapp, if requested
112 # parse_command_line will initialize a subapp, if requested
113 subapp = Instance('IPython.config.application.Application', allow_none=True)
113 subapp = Instance('IPython.config.application.Application', allow_none=True)
114
114
115 # extra command-line arguments that don't set config values
116 extra_args = List(Unicode)
117
115
118
116 def __init__(self, **kwargs):
119 def __init__(self, **kwargs):
117 SingletonConfigurable.__init__(self, **kwargs)
120 SingletonConfigurable.__init__(self, **kwargs)
118 # Add my class to self.classes so my attributes appear in command line
121 # Add my class to self.classes so my attributes appear in command line
119 # options.
122 # options.
120 self.classes.insert(0, self.__class__)
123 self.classes.insert(0, self.__class__)
121
124
122 # ensure self.flags dict is valid
125 # ensure self.flags dict is valid
123 for key,value in self.flags.iteritems():
126 for key,value in self.flags.iteritems():
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
127 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
128 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
129 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
127 self.init_logging()
130 self.init_logging()
128
131
129 def _config_changed(self, name, old, new):
132 def _config_changed(self, name, old, new):
130 SingletonConfigurable._config_changed(self, name, old, new)
133 SingletonConfigurable._config_changed(self, name, old, new)
131 self.log.debug('Config changed:')
134 self.log.debug('Config changed:')
132 self.log.debug(repr(new))
135 self.log.debug(repr(new))
133
136
134 def init_logging(self):
137 def init_logging(self):
135 """Start logging for this application.
138 """Start logging for this application.
136
139
137 The default is to log to stdout using a StreaHandler. The log level
140 The default is to log to stdout using a StreaHandler. The log level
138 starts at loggin.WARN, but this can be adjusted by setting the
141 starts at loggin.WARN, but this can be adjusted by setting the
139 ``log_level`` attribute.
142 ``log_level`` attribute.
140 """
143 """
141 self.log = logging.getLogger(self.__class__.__name__)
144 self.log = logging.getLogger(self.__class__.__name__)
142 self.log.setLevel(self.log_level)
145 self.log.setLevel(self.log_level)
143 self._log_handler = logging.StreamHandler()
146 self._log_handler = logging.StreamHandler()
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 self._log_handler.setFormatter(self._log_formatter)
148 self._log_handler.setFormatter(self._log_formatter)
146 self.log.addHandler(self._log_handler)
149 self.log.addHandler(self._log_handler)
147
150
148 def initialize(self, argv=None):
151 def initialize(self, argv=None):
149 """Do the basic steps to configure me.
152 """Do the basic steps to configure me.
150
153
151 Override in subclasses.
154 Override in subclasses.
152 """
155 """
153 self.parse_command_line(argv)
156 self.parse_command_line(argv)
154
157
155
158
156 def start(self):
159 def start(self):
157 """Start the app mainloop.
160 """Start the app mainloop.
158
161
159 Override in subclasses.
162 Override in subclasses.
160 """
163 """
161 if self.subapp is not None:
164 if self.subapp is not None:
162 return self.subapp.start()
165 return self.subapp.start()
163
166
164 def _log_level_changed(self, name, old, new):
167 def _log_level_changed(self, name, old, new):
165 """Adjust the log level when log_level is set."""
168 """Adjust the log level when log_level is set."""
166 self.log.setLevel(new)
169 self.log.setLevel(new)
167
170
168 def print_alias_help(self):
171 def print_alias_help(self):
169 """print the alias part of the help"""
172 """print the alias part of the help"""
170 if not self.aliases:
173 if not self.aliases:
171 return
174 return
172
175
173 lines = ['Aliases']
176 lines = ['Aliases']
174 lines.append('-'*len(lines[0]))
177 lines.append('-'*len(lines[0]))
175 lines.append(self.alias_description)
178 lines.append(self.alias_description)
176 lines.append('')
179 lines.append('')
177
180
178 classdict = {}
181 classdict = {}
179 for cls in self.classes:
182 for cls in self.classes:
180 # include all parents (up to, but excluding Configurable) in available names
183 # include all parents (up to, but excluding Configurable) in available names
181 for c in cls.mro()[:-3]:
184 for c in cls.mro()[:-3]:
182 classdict[c.__name__] = c
185 classdict[c.__name__] = c
183
186
184 for alias, longname in self.aliases.iteritems():
187 for alias, longname in self.aliases.iteritems():
185 classname, traitname = longname.split('.',1)
188 classname, traitname = longname.split('.',1)
186 cls = classdict[classname]
189 cls = classdict[classname]
187
190
188 trait = cls.class_traits(config=True)[traitname]
191 trait = cls.class_traits(config=True)[traitname]
189 help = cls.class_get_trait_help(trait)
192 help = cls.class_get_trait_help(trait)
190 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
193 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
191 lines.append(help)
194 lines.append(help)
192 lines.append('')
195 lines.append('')
193 print '\n'.join(lines)
196 print '\n'.join(lines)
194
197
195 def print_flag_help(self):
198 def print_flag_help(self):
196 """print the flag part of the help"""
199 """print the flag part of the help"""
197 if not self.flags:
200 if not self.flags:
198 return
201 return
199
202
200 lines = ['Flags']
203 lines = ['Flags']
201 lines.append('-'*len(lines[0]))
204 lines.append('-'*len(lines[0]))
202 lines.append(self.flag_description)
205 lines.append(self.flag_description)
203 lines.append('')
206 lines.append('')
204
207
205 for m, (cfg,help) in self.flags.iteritems():
208 for m, (cfg,help) in self.flags.iteritems():
206 lines.append('--'+m)
209 lines.append('--'+m)
207 lines.append(indent(help, flatten=True))
210 lines.append(indent(help, flatten=True))
208 lines.append('')
211 lines.append('')
209 print '\n'.join(lines)
212 print '\n'.join(lines)
210
213
211 def print_subcommands(self):
214 def print_subcommands(self):
212 """print the subcommand part of the help"""
215 """print the subcommand part of the help"""
213 if not self.subcommands:
216 if not self.subcommands:
214 return
217 return
215
218
216 lines = ["Subcommands"]
219 lines = ["Subcommands"]
217 lines.append('-'*len(lines[0]))
220 lines.append('-'*len(lines[0]))
218 for subc, (cls,help) in self.subcommands.iteritems():
221 for subc, (cls,help) in self.subcommands.iteritems():
219 lines.append("%s : %s"%(subc, cls))
222 lines.append("%s : %s"%(subc, cls))
220 if help:
223 if help:
221 lines.append(indent(help, flatten=True))
224 lines.append(indent(help, flatten=True))
222 lines.append('')
225 lines.append('')
223 print '\n'.join(lines)
226 print '\n'.join(lines)
224
227
225 def print_help(self, classes=False):
228 def print_help(self, classes=False):
226 """Print the help for each Configurable class in self.classes.
229 """Print the help for each Configurable class in self.classes.
227
230
228 If classes=False (the default), only flags and aliases are printed
231 If classes=False (the default), only flags and aliases are printed
229 """
232 """
230 self.print_subcommands()
233 self.print_subcommands()
231 self.print_flag_help()
234 self.print_flag_help()
232 self.print_alias_help()
235 self.print_alias_help()
233
236
234 if classes:
237 if classes:
235 if self.classes:
238 if self.classes:
236 print "Class parameters"
239 print "Class parameters"
237 print "----------------"
240 print "----------------"
238 print self.keyvalue_description
241 print self.keyvalue_description
239 print
242 print
240
243
241 for cls in self.classes:
244 for cls in self.classes:
242 cls.class_print_help()
245 cls.class_print_help()
243 print
246 print
244 else:
247 else:
245 print "To see all available configurables, use `--help-all`"
248 print "To see all available configurables, use `--help-all`"
246 print
249 print
247
250
248 def print_description(self):
251 def print_description(self):
249 """Print the application description."""
252 """Print the application description."""
250 print self.description
253 print self.description
251 print
254 print
252
255
253 def print_version(self):
256 def print_version(self):
254 """Print the version string."""
257 """Print the version string."""
255 print self.version
258 print self.version
256
259
257 def update_config(self, config):
260 def update_config(self, config):
258 """Fire the traits events when the config is updated."""
261 """Fire the traits events when the config is updated."""
259 # Save a copy of the current config.
262 # Save a copy of the current config.
260 newconfig = deepcopy(self.config)
263 newconfig = deepcopy(self.config)
261 # Merge the new config into the current one.
264 # Merge the new config into the current one.
262 newconfig._merge(config)
265 newconfig._merge(config)
263 # Save the combined config as self.config, which triggers the traits
266 # Save the combined config as self.config, which triggers the traits
264 # events.
267 # events.
265 self.config = newconfig
268 self.config = newconfig
266
269
267 def initialize_subcommand(self, subc, argv=None):
270 def initialize_subcommand(self, subc, argv=None):
268 """Initialize a subcommand with argv"""
271 """Initialize a subcommand with argv"""
269 subapp,help = self.subcommands.get(subc, (None,None))
272 subapp,help = self.subcommands.get(subc)
270 if subapp is None:
271 self.print_description()
272 print "No such subcommand: %r"%subc
273 print
274 self.print_subcommands()
275 self.exit(1)
276
273
277 if isinstance(subapp, basestring):
274 if isinstance(subapp, basestring):
278 subapp = import_item(subapp)
275 subapp = import_item(subapp)
279
276
280 # instantiate
277 # instantiate
281 self.subapp = subapp()
278 self.subapp = subapp()
282 # and initialize subapp
279 # and initialize subapp
283 self.subapp.initialize(argv)
280 self.subapp.initialize(argv)
284
281
285 def parse_command_line(self, argv=None):
282 def parse_command_line(self, argv=None):
286 """Parse the command line arguments."""
283 """Parse the command line arguments."""
287 argv = sys.argv[1:] if argv is None else argv
284 argv = sys.argv[1:] if argv is None else argv
288
285
289 if self.subcommands and len(argv) > 0:
286 if self.subcommands and len(argv) > 0:
290 # we have subcommands, and one may have been specified
287 # we have subcommands, and one may have been specified
291 subc, subargv = argv[0], argv[1:]
288 subc, subargv = argv[0], argv[1:]
292 if re.match(r'^\w(\-?\w)*$', subc):
289 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
293 # it's a subcommand, and *not* a flag or class parameter
290 # it's a subcommand, and *not* a flag or class parameter
294 return self.initialize_subcommand(subc, subargv)
291 return self.initialize_subcommand(subc, subargv)
295
292
296 if '-h' in argv or '--help' in argv or '--help-all' in argv:
293 if '-h' in argv or '--help' in argv or '--help-all' in argv:
297 self.print_description()
294 self.print_description()
298 self.print_help('--help-all' in argv)
295 self.print_help('--help-all' in argv)
299 self.exit(0)
296 self.exit(0)
300
297
301 if '--version' in argv:
298 if '--version' in argv:
302 self.print_version()
299 self.print_version()
303 self.exit(0)
300 self.exit(0)
304
301
305 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
302 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
306 flags=self.flags)
303 flags=self.flags)
307 try:
304 try:
308 config = loader.load_config()
305 config = loader.load_config()
309 except ArgumentError as e:
306 except ArgumentError as e:
310 self.log.fatal(str(e))
307 self.log.fatal(str(e))
311 self.print_description()
308 self.print_description()
312 self.print_help()
309 self.print_help()
313 self.exit(1)
310 self.exit(1)
314 self.update_config(config)
311 self.update_config(config)
312 # store unparsed args in extra_args
313 self.extra_args = loader.extra_args
315
314
316 def load_config_file(self, filename, path=None):
315 def load_config_file(self, filename, path=None):
317 """Load a .py based config file by filename and path."""
316 """Load a .py based config file by filename and path."""
318 loader = PyFileConfigLoader(filename, path=path)
317 loader = PyFileConfigLoader(filename, path=path)
319 config = loader.load_config()
318 config = loader.load_config()
320 self.update_config(config)
319 self.update_config(config)
321
320
322 def exit(self, exit_status=0):
321 def exit(self, exit_status=0):
323 self.log.debug("Exiting application: %s" % self.name)
322 self.log.debug("Exiting application: %s" % self.name)
324 sys.exit(exit_status)
323 sys.exit(exit_status)
325
324
326 #-----------------------------------------------------------------------------
325 #-----------------------------------------------------------------------------
327 # utility functions, for convenience
326 # utility functions, for convenience
328 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
329
328
330 def boolean_flag(name, configurable, set_help='', unset_help=''):
329 def boolean_flag(name, configurable, set_help='', unset_help=''):
331 """helper for building basic --trait, --no-trait flags
330 """helper for building basic --trait, --no-trait flags
332
331
333 Parameters
332 Parameters
334 ----------
333 ----------
335
334
336 name : str
335 name : str
337 The name of the flag.
336 The name of the flag.
338 configurable : str
337 configurable : str
339 The 'Class.trait' string of the trait to be set/unset with the flag
338 The 'Class.trait' string of the trait to be set/unset with the flag
340 set_help : unicode
339 set_help : unicode
341 help string for --name flag
340 help string for --name flag
342 unset_help : unicode
341 unset_help : unicode
343 help string for --no-name flag
342 help string for --no-name flag
344
343
345 Returns
344 Returns
346 -------
345 -------
347
346
348 cfg : dict
347 cfg : dict
349 A dict with two keys: 'name', and 'no-name', for setting and unsetting
348 A dict with two keys: 'name', and 'no-name', for setting and unsetting
350 the trait, respectively.
349 the trait, respectively.
351 """
350 """
352 # default helpstrings
351 # default helpstrings
353 set_help = set_help or "set %s=True"%configurable
352 set_help = set_help or "set %s=True"%configurable
354 unset_help = unset_help or "set %s=False"%configurable
353 unset_help = unset_help or "set %s=False"%configurable
355
354
356 cls,trait = configurable.split('.')
355 cls,trait = configurable.split('.')
357
356
358 setter = {cls : {trait : True}}
357 setter = {cls : {trait : True}}
359 unsetter = {cls : {trait : False}}
358 unsetter = {cls : {trait : False}}
360 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
359 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
@@ -1,506 +1,513 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 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2009 The IPython Development Team
10 # Copyright (C) 2008-2009 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 import __builtin__
20 import __builtin__
21 import re
21 import re
22 import sys
22 import sys
23
23
24 from IPython.external import argparse
24 from IPython.external import argparse
25 from IPython.utils.path import filefind
25 from IPython.utils.path import filefind
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Exceptions
28 # Exceptions
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class ConfigError(Exception):
32 class ConfigError(Exception):
33 pass
33 pass
34
34
35
35
36 class ConfigLoaderError(ConfigError):
36 class ConfigLoaderError(ConfigError):
37 pass
37 pass
38
38
39 class ArgumentError(ConfigLoaderError):
39 class ArgumentError(ConfigLoaderError):
40 pass
40 pass
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Argparse fix
43 # Argparse fix
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 # Unfortunately argparse by default prints help messages to stderr instead of
46 # Unfortunately argparse by default prints help messages to stderr instead of
47 # stdout. This makes it annoying to capture long help screens at the command
47 # stdout. This makes it annoying to capture long help screens at the command
48 # line, since one must know how to pipe stderr, which many users don't know how
48 # line, since one must know how to pipe stderr, which many users don't know how
49 # to do. So we override the print_help method with one that defaults to
49 # to do. So we override the print_help method with one that defaults to
50 # stdout and use our class instead.
50 # stdout and use our class instead.
51
51
52 class ArgumentParser(argparse.ArgumentParser):
52 class ArgumentParser(argparse.ArgumentParser):
53 """Simple argparse subclass that prints help to stdout by default."""
53 """Simple argparse subclass that prints help to stdout by default."""
54
54
55 def print_help(self, file=None):
55 def print_help(self, file=None):
56 if file is None:
56 if file is None:
57 file = sys.stdout
57 file = sys.stdout
58 return super(ArgumentParser, self).print_help(file)
58 return super(ArgumentParser, self).print_help(file)
59
59
60 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
60 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Config class for holding config information
63 # Config class for holding config information
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66
66
67 class Config(dict):
67 class Config(dict):
68 """An attribute based dict that can do smart merges."""
68 """An attribute based dict that can do smart merges."""
69
69
70 def __init__(self, *args, **kwds):
70 def __init__(self, *args, **kwds):
71 dict.__init__(self, *args, **kwds)
71 dict.__init__(self, *args, **kwds)
72 # This sets self.__dict__ = self, but it has to be done this way
72 # This sets self.__dict__ = self, but it has to be done this way
73 # because we are also overriding __setattr__.
73 # because we are also overriding __setattr__.
74 dict.__setattr__(self, '__dict__', self)
74 dict.__setattr__(self, '__dict__', self)
75
75
76 def _merge(self, other):
76 def _merge(self, other):
77 to_update = {}
77 to_update = {}
78 for k, v in other.iteritems():
78 for k, v in other.iteritems():
79 if not self.has_key(k):
79 if not self.has_key(k):
80 to_update[k] = v
80 to_update[k] = v
81 else: # I have this key
81 else: # I have this key
82 if isinstance(v, Config):
82 if isinstance(v, Config):
83 # Recursively merge common sub Configs
83 # Recursively merge common sub Configs
84 self[k]._merge(v)
84 self[k]._merge(v)
85 else:
85 else:
86 # Plain updates for non-Configs
86 # Plain updates for non-Configs
87 to_update[k] = v
87 to_update[k] = v
88
88
89 self.update(to_update)
89 self.update(to_update)
90
90
91 def _is_section_key(self, key):
91 def _is_section_key(self, key):
92 if key[0].upper()==key[0] and not key.startswith('_'):
92 if key[0].upper()==key[0] and not key.startswith('_'):
93 return True
93 return True
94 else:
94 else:
95 return False
95 return False
96
96
97 def __contains__(self, key):
97 def __contains__(self, key):
98 if self._is_section_key(key):
98 if self._is_section_key(key):
99 return True
99 return True
100 else:
100 else:
101 return super(Config, self).__contains__(key)
101 return super(Config, self).__contains__(key)
102 # .has_key is deprecated for dictionaries.
102 # .has_key is deprecated for dictionaries.
103 has_key = __contains__
103 has_key = __contains__
104
104
105 def _has_section(self, key):
105 def _has_section(self, key):
106 if self._is_section_key(key):
106 if self._is_section_key(key):
107 if super(Config, self).__contains__(key):
107 if super(Config, self).__contains__(key):
108 return True
108 return True
109 return False
109 return False
110
110
111 def copy(self):
111 def copy(self):
112 return type(self)(dict.copy(self))
112 return type(self)(dict.copy(self))
113
113
114 def __copy__(self):
114 def __copy__(self):
115 return self.copy()
115 return self.copy()
116
116
117 def __deepcopy__(self, memo):
117 def __deepcopy__(self, memo):
118 import copy
118 import copy
119 return type(self)(copy.deepcopy(self.items()))
119 return type(self)(copy.deepcopy(self.items()))
120
120
121 def __getitem__(self, key):
121 def __getitem__(self, key):
122 # We cannot use directly self._is_section_key, because it triggers
122 # We cannot use directly self._is_section_key, because it triggers
123 # infinite recursion on top of PyPy. Instead, we manually fish the
123 # infinite recursion on top of PyPy. Instead, we manually fish the
124 # bound method.
124 # bound method.
125 is_section_key = self.__class__._is_section_key.__get__(self)
125 is_section_key = self.__class__._is_section_key.__get__(self)
126
126
127 # Because we use this for an exec namespace, we need to delegate
127 # Because we use this for an exec namespace, we need to delegate
128 # the lookup of names in __builtin__ to itself. This means
128 # the lookup of names in __builtin__ to itself. This means
129 # that you can't have section or attribute names that are
129 # that you can't have section or attribute names that are
130 # builtins.
130 # builtins.
131 try:
131 try:
132 return getattr(__builtin__, key)
132 return getattr(__builtin__, key)
133 except AttributeError:
133 except AttributeError:
134 pass
134 pass
135 if is_section_key(key):
135 if is_section_key(key):
136 try:
136 try:
137 return dict.__getitem__(self, key)
137 return dict.__getitem__(self, key)
138 except KeyError:
138 except KeyError:
139 c = Config()
139 c = Config()
140 dict.__setitem__(self, key, c)
140 dict.__setitem__(self, key, c)
141 return c
141 return c
142 else:
142 else:
143 return dict.__getitem__(self, key)
143 return dict.__getitem__(self, key)
144
144
145 def __setitem__(self, key, value):
145 def __setitem__(self, key, value):
146 # Don't allow names in __builtin__ to be modified.
146 # Don't allow names in __builtin__ to be modified.
147 if hasattr(__builtin__, key):
147 if hasattr(__builtin__, key):
148 raise ConfigError('Config variable names cannot have the same name '
148 raise ConfigError('Config variable names cannot have the same name '
149 'as a Python builtin: %s' % key)
149 'as a Python builtin: %s' % key)
150 if self._is_section_key(key):
150 if self._is_section_key(key):
151 if not isinstance(value, Config):
151 if not isinstance(value, Config):
152 raise ValueError('values whose keys begin with an uppercase '
152 raise ValueError('values whose keys begin with an uppercase '
153 'char must be Config instances: %r, %r' % (key, value))
153 'char must be Config instances: %r, %r' % (key, value))
154 else:
154 else:
155 dict.__setitem__(self, key, value)
155 dict.__setitem__(self, key, value)
156
156
157 def __getattr__(self, key):
157 def __getattr__(self, key):
158 try:
158 try:
159 return self.__getitem__(key)
159 return self.__getitem__(key)
160 except KeyError, e:
160 except KeyError, e:
161 raise AttributeError(e)
161 raise AttributeError(e)
162
162
163 def __setattr__(self, key, value):
163 def __setattr__(self, key, value):
164 try:
164 try:
165 self.__setitem__(key, value)
165 self.__setitem__(key, value)
166 except KeyError, e:
166 except KeyError, e:
167 raise AttributeError(e)
167 raise AttributeError(e)
168
168
169 def __delattr__(self, key):
169 def __delattr__(self, key):
170 try:
170 try:
171 dict.__delitem__(self, key)
171 dict.__delitem__(self, key)
172 except KeyError, e:
172 except KeyError, e:
173 raise AttributeError(e)
173 raise AttributeError(e)
174
174
175
175
176 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
177 # Config loading classes
177 # Config loading classes
178 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
179
179
180
180
181 class ConfigLoader(object):
181 class ConfigLoader(object):
182 """A object for loading configurations from just about anywhere.
182 """A object for loading configurations from just about anywhere.
183
183
184 The resulting configuration is packaged as a :class:`Struct`.
184 The resulting configuration is packaged as a :class:`Struct`.
185
185
186 Notes
186 Notes
187 -----
187 -----
188 A :class:`ConfigLoader` does one thing: load a config from a source
188 A :class:`ConfigLoader` does one thing: load a config from a source
189 (file, command line arguments) and returns the data as a :class:`Struct`.
189 (file, command line arguments) and returns the data as a :class:`Struct`.
190 There are lots of things that :class:`ConfigLoader` does not do. It does
190 There are lots of things that :class:`ConfigLoader` does not do. It does
191 not implement complex logic for finding config files. It does not handle
191 not implement complex logic for finding config files. It does not handle
192 default values or merge multiple configs. These things need to be
192 default values or merge multiple configs. These things need to be
193 handled elsewhere.
193 handled elsewhere.
194 """
194 """
195
195
196 def __init__(self):
196 def __init__(self):
197 """A base class for config loaders.
197 """A base class for config loaders.
198
198
199 Examples
199 Examples
200 --------
200 --------
201
201
202 >>> cl = ConfigLoader()
202 >>> cl = ConfigLoader()
203 >>> config = cl.load_config()
203 >>> config = cl.load_config()
204 >>> config
204 >>> config
205 {}
205 {}
206 """
206 """
207 self.clear()
207 self.clear()
208
208
209 def clear(self):
209 def clear(self):
210 self.config = Config()
210 self.config = Config()
211
211
212 def load_config(self):
212 def load_config(self):
213 """Load a config from somewhere, return a :class:`Config` instance.
213 """Load a config from somewhere, return a :class:`Config` instance.
214
214
215 Usually, this will cause self.config to be set and then returned.
215 Usually, this will cause self.config to be set and then returned.
216 However, in most cases, :meth:`ConfigLoader.clear` should be called
216 However, in most cases, :meth:`ConfigLoader.clear` should be called
217 to erase any previous state.
217 to erase any previous state.
218 """
218 """
219 self.clear()
219 self.clear()
220 return self.config
220 return self.config
221
221
222
222
223 class FileConfigLoader(ConfigLoader):
223 class FileConfigLoader(ConfigLoader):
224 """A base class for file based configurations.
224 """A base class for file based configurations.
225
225
226 As we add more file based config loaders, the common logic should go
226 As we add more file based config loaders, the common logic should go
227 here.
227 here.
228 """
228 """
229 pass
229 pass
230
230
231
231
232 class PyFileConfigLoader(FileConfigLoader):
232 class PyFileConfigLoader(FileConfigLoader):
233 """A config loader for pure python files.
233 """A config loader for pure python files.
234
234
235 This calls execfile on a plain python file and looks for attributes
235 This calls execfile on a plain python file and looks for attributes
236 that are all caps. These attribute are added to the config Struct.
236 that are all caps. These attribute are added to the config Struct.
237 """
237 """
238
238
239 def __init__(self, filename, path=None):
239 def __init__(self, filename, path=None):
240 """Build a config loader for a filename and path.
240 """Build a config loader for a filename and path.
241
241
242 Parameters
242 Parameters
243 ----------
243 ----------
244 filename : str
244 filename : str
245 The file name of the config file.
245 The file name of the config file.
246 path : str, list, tuple
246 path : str, list, tuple
247 The path to search for the config file on, or a sequence of
247 The path to search for the config file on, or a sequence of
248 paths to try in order.
248 paths to try in order.
249 """
249 """
250 super(PyFileConfigLoader, self).__init__()
250 super(PyFileConfigLoader, self).__init__()
251 self.filename = filename
251 self.filename = filename
252 self.path = path
252 self.path = path
253 self.full_filename = ''
253 self.full_filename = ''
254 self.data = None
254 self.data = None
255
255
256 def load_config(self):
256 def load_config(self):
257 """Load the config from a file and return it as a Struct."""
257 """Load the config from a file and return it as a Struct."""
258 self.clear()
258 self.clear()
259 self._find_file()
259 self._find_file()
260 self._read_file_as_dict()
260 self._read_file_as_dict()
261 self._convert_to_config()
261 self._convert_to_config()
262 return self.config
262 return self.config
263
263
264 def _find_file(self):
264 def _find_file(self):
265 """Try to find the file by searching the paths."""
265 """Try to find the file by searching the paths."""
266 self.full_filename = filefind(self.filename, self.path)
266 self.full_filename = filefind(self.filename, self.path)
267
267
268 def _read_file_as_dict(self):
268 def _read_file_as_dict(self):
269 """Load the config file into self.config, with recursive loading."""
269 """Load the config file into self.config, with recursive loading."""
270 # This closure is made available in the namespace that is used
270 # This closure is made available in the namespace that is used
271 # to exec the config file. This allows users to call
271 # to exec the config file. This allows users to call
272 # load_subconfig('myconfig.py') to load config files recursively.
272 # load_subconfig('myconfig.py') to load config files recursively.
273 # It needs to be a closure because it has references to self.path
273 # It needs to be a closure because it has references to self.path
274 # and self.config. The sub-config is loaded with the same path
274 # and self.config. The sub-config is loaded with the same path
275 # as the parent, but it uses an empty config which is then merged
275 # as the parent, but it uses an empty config which is then merged
276 # with the parents.
276 # with the parents.
277 def load_subconfig(fname):
277 def load_subconfig(fname):
278 loader = PyFileConfigLoader(fname, self.path)
278 loader = PyFileConfigLoader(fname, self.path)
279 try:
279 try:
280 sub_config = loader.load_config()
280 sub_config = loader.load_config()
281 except IOError:
281 except IOError:
282 # Pass silently if the sub config is not there. This happens
282 # Pass silently if the sub config is not there. This happens
283 # when a user us using a profile, but not the default config.
283 # when a user us using a profile, but not the default config.
284 pass
284 pass
285 else:
285 else:
286 self.config._merge(sub_config)
286 self.config._merge(sub_config)
287
287
288 # Again, this needs to be a closure and should be used in config
288 # Again, this needs to be a closure and should be used in config
289 # files to get the config being loaded.
289 # files to get the config being loaded.
290 def get_config():
290 def get_config():
291 return self.config
291 return self.config
292
292
293 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
293 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
294 fs_encoding = sys.getfilesystemencoding() or 'ascii'
294 fs_encoding = sys.getfilesystemencoding() or 'ascii'
295 conf_filename = self.full_filename.encode(fs_encoding)
295 conf_filename = self.full_filename.encode(fs_encoding)
296 execfile(conf_filename, namespace)
296 execfile(conf_filename, namespace)
297
297
298 def _convert_to_config(self):
298 def _convert_to_config(self):
299 if self.data is None:
299 if self.data is None:
300 ConfigLoaderError('self.data does not exist')
300 ConfigLoaderError('self.data does not exist')
301
301
302
302
303 class CommandLineConfigLoader(ConfigLoader):
303 class CommandLineConfigLoader(ConfigLoader):
304 """A config loader for command line arguments.
304 """A config loader for command line arguments.
305
305
306 As we add more command line based loaders, the common logic should go
306 As we add more command line based loaders, the common logic should go
307 here.
307 here.
308 """
308 """
309
309
310 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
310 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
311 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
311 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
312
312
313 class KeyValueConfigLoader(CommandLineConfigLoader):
313 class KeyValueConfigLoader(CommandLineConfigLoader):
314 """A config loader that loads key value pairs from the command line.
314 """A config loader that loads key value pairs from the command line.
315
315
316 This allows command line options to be gives in the following form::
316 This allows command line options to be gives in the following form::
317
317
318 ipython Global.profile="foo" InteractiveShell.autocall=False
318 ipython Global.profile="foo" InteractiveShell.autocall=False
319 """
319 """
320
320
321 def __init__(self, argv=None, aliases=None, flags=None):
321 def __init__(self, argv=None, aliases=None, flags=None):
322 """Create a key value pair config loader.
322 """Create a key value pair config loader.
323
323
324 Parameters
324 Parameters
325 ----------
325 ----------
326 argv : list
326 argv : list
327 A list that has the form of sys.argv[1:] which has unicode
327 A list that has the form of sys.argv[1:] which has unicode
328 elements of the form u"key=value". If this is None (default),
328 elements of the form u"key=value". If this is None (default),
329 then sys.argv[1:] will be used.
329 then sys.argv[1:] will be used.
330 aliases : dict
330 aliases : dict
331 A dict of aliases for configurable traits.
331 A dict of aliases for configurable traits.
332 Keys are the short aliases, Values are the resolved trait.
332 Keys are the short aliases, Values are the resolved trait.
333 Of the form: `{'alias' : 'Configurable.trait'}`
333 Of the form: `{'alias' : 'Configurable.trait'}`
334 flags : dict
334 flags : dict
335 A dict of flags, keyed by str name. Vaues can be Config objects,
335 A dict of flags, keyed by str name. Vaues can be Config objects,
336 dicts, or "key=value" strings. If Config or dict, when the flag
336 dicts, or "key=value" strings. If Config or dict, when the flag
337 is triggered, The flag is loaded as `self.config.update(m)`.
337 is triggered, The flag is loaded as `self.config.update(m)`.
338
338
339 Returns
339 Returns
340 -------
340 -------
341 config : Config
341 config : Config
342 The resulting Config object.
342 The resulting Config object.
343
343
344 Examples
344 Examples
345 --------
345 --------
346
346
347 >>> from IPython.config.loader import KeyValueConfigLoader
347 >>> from IPython.config.loader import KeyValueConfigLoader
348 >>> cl = KeyValueConfigLoader()
348 >>> cl = KeyValueConfigLoader()
349 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
349 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
350 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
350 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
351 """
351 """
352 if argv is None:
352 if argv is None:
353 argv = sys.argv[1:]
353 argv = sys.argv[1:]
354 self.argv = argv
354 self.argv = argv
355 self.aliases = aliases or {}
355 self.aliases = aliases or {}
356 self.flags = flags or {}
356 self.flags = flags or {}
357
357
358 def load_config(self, argv=None, aliases=None, flags=None):
358 def load_config(self, argv=None, aliases=None, flags=None):
359 """Parse the configuration and generate the Config object.
359 """Parse the configuration and generate the Config object.
360
360
361 Parameters
361 Parameters
362 ----------
362 ----------
363 argv : list, optional
363 argv : list, optional
364 A list that has the form of sys.argv[1:] which has unicode
364 A list that has the form of sys.argv[1:] which has unicode
365 elements of the form u"key=value". If this is None (default),
365 elements of the form u"key=value". If this is None (default),
366 then self.argv will be used.
366 then self.argv will be used.
367 aliases : dict
367 aliases : dict
368 A dict of aliases for configurable traits.
368 A dict of aliases for configurable traits.
369 Keys are the short aliases, Values are the resolved trait.
369 Keys are the short aliases, Values are the resolved trait.
370 Of the form: `{'alias' : 'Configurable.trait'}`
370 Of the form: `{'alias' : 'Configurable.trait'}`
371 flags : dict
371 flags : dict
372 A dict of flags, keyed by str name. Values can be Config objects
372 A dict of flags, keyed by str name. Values can be Config objects
373 or dicts. When the flag is triggered, The config is loaded as
373 or dicts. When the flag is triggered, The config is loaded as
374 `self.config.update(cfg)`.
374 `self.config.update(cfg)`.
375 """
375 """
376 from IPython.config.configurable import Configurable
376 from IPython.config.configurable import Configurable
377
377
378 self.clear()
378 self.clear()
379 if argv is None:
379 if argv is None:
380 argv = self.argv
380 argv = self.argv
381 if aliases is None:
381 if aliases is None:
382 aliases = self.aliases
382 aliases = self.aliases
383 if flags is None:
383 if flags is None:
384 flags = self.flags
384 flags = self.flags
385
385
386 self.extra_args = []
387
386 for item in argv:
388 for item in argv:
387 if kv_pattern.match(item):
389 if kv_pattern.match(item):
388 lhs,rhs = item.split('=',1)
390 lhs,rhs = item.split('=',1)
389 # Substitute longnames for aliases.
391 # Substitute longnames for aliases.
390 if lhs in aliases:
392 if lhs in aliases:
391 lhs = aliases[lhs]
393 lhs = aliases[lhs]
392 exec_str = 'self.config.' + lhs + '=' + rhs
394 exec_str = 'self.config.' + lhs + '=' + rhs
393 try:
395 try:
394 # Try to see if regular Python syntax will work. This
396 # Try to see if regular Python syntax will work. This
395 # won't handle strings as the quote marks are removed
397 # won't handle strings as the quote marks are removed
396 # by the system shell.
398 # by the system shell.
397 exec exec_str in locals(), globals()
399 exec exec_str in locals(), globals()
398 except (NameError, SyntaxError):
400 except (NameError, SyntaxError):
399 # This case happens if the rhs is a string but without
401 # This case happens if the rhs is a string but without
400 # the quote marks. We add the quote marks and see if
402 # the quote marks. We add the quote marks and see if
401 # it succeeds. If it still fails, we let it raise.
403 # it succeeds. If it still fails, we let it raise.
402 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
404 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
403 exec exec_str in locals(), globals()
405 exec exec_str in locals(), globals()
404 elif flag_pattern.match(item):
406 elif flag_pattern.match(item):
405 # trim leading '--'
407 # trim leading '--'
406 m = item[2:]
408 m = item[2:]
407 cfg,_ = flags.get(m, (None,None))
409 cfg,_ = flags.get(m, (None,None))
408 if cfg is None:
410 if cfg is None:
409 raise ArgumentError("Unrecognized flag: %r"%item)
411 raise ArgumentError("Unrecognized flag: %r"%item)
410 elif isinstance(cfg, (dict, Config)):
412 elif isinstance(cfg, (dict, Config)):
411 # don't clobber whole config sections, update
413 # don't clobber whole config sections, update
412 # each section from config:
414 # each section from config:
413 for sec,c in cfg.iteritems():
415 for sec,c in cfg.iteritems():
414 self.config[sec].update(c)
416 self.config[sec].update(c)
415 else:
417 else:
416 raise ValueError("Invalid flag: %r"%flag)
418 raise ValueError("Invalid flag: %r"%flag)
417 else:
419 elif item.startswith('-'):
420 # this shouldn't ever be valid
418 raise ArgumentError("Invalid argument: %r"%item)
421 raise ArgumentError("Invalid argument: %r"%item)
422 else:
423 # keep all args that aren't valid in a list,
424 # in case our parent knows what to do with them.
425 self.extra_args.append(item)
419 return self.config
426 return self.config
420
427
421 class ArgParseConfigLoader(CommandLineConfigLoader):
428 class ArgParseConfigLoader(CommandLineConfigLoader):
422 """A loader that uses the argparse module to load from the command line."""
429 """A loader that uses the argparse module to load from the command line."""
423
430
424 def __init__(self, argv=None, *parser_args, **parser_kw):
431 def __init__(self, argv=None, *parser_args, **parser_kw):
425 """Create a config loader for use with argparse.
432 """Create a config loader for use with argparse.
426
433
427 Parameters
434 Parameters
428 ----------
435 ----------
429
436
430 argv : optional, list
437 argv : optional, list
431 If given, used to read command-line arguments from, otherwise
438 If given, used to read command-line arguments from, otherwise
432 sys.argv[1:] is used.
439 sys.argv[1:] is used.
433
440
434 parser_args : tuple
441 parser_args : tuple
435 A tuple of positional arguments that will be passed to the
442 A tuple of positional arguments that will be passed to the
436 constructor of :class:`argparse.ArgumentParser`.
443 constructor of :class:`argparse.ArgumentParser`.
437
444
438 parser_kw : dict
445 parser_kw : dict
439 A tuple of keyword arguments that will be passed to the
446 A tuple of keyword arguments that will be passed to the
440 constructor of :class:`argparse.ArgumentParser`.
447 constructor of :class:`argparse.ArgumentParser`.
441
448
442 Returns
449 Returns
443 -------
450 -------
444 config : Config
451 config : Config
445 The resulting Config object.
452 The resulting Config object.
446 """
453 """
447 super(CommandLineConfigLoader, self).__init__()
454 super(CommandLineConfigLoader, self).__init__()
448 if argv == None:
455 if argv == None:
449 argv = sys.argv[1:]
456 argv = sys.argv[1:]
450 self.argv = argv
457 self.argv = argv
451 self.parser_args = parser_args
458 self.parser_args = parser_args
452 self.version = parser_kw.pop("version", None)
459 self.version = parser_kw.pop("version", None)
453 kwargs = dict(argument_default=argparse.SUPPRESS)
460 kwargs = dict(argument_default=argparse.SUPPRESS)
454 kwargs.update(parser_kw)
461 kwargs.update(parser_kw)
455 self.parser_kw = kwargs
462 self.parser_kw = kwargs
456
463
457 def load_config(self, argv=None):
464 def load_config(self, argv=None):
458 """Parse command line arguments and return as a Config object.
465 """Parse command line arguments and return as a Config object.
459
466
460 Parameters
467 Parameters
461 ----------
468 ----------
462
469
463 args : optional, list
470 args : optional, list
464 If given, a list with the structure of sys.argv[1:] to parse
471 If given, a list with the structure of sys.argv[1:] to parse
465 arguments from. If not given, the instance's self.argv attribute
472 arguments from. If not given, the instance's self.argv attribute
466 (given at construction time) is used."""
473 (given at construction time) is used."""
467 self.clear()
474 self.clear()
468 if argv is None:
475 if argv is None:
469 argv = self.argv
476 argv = self.argv
470 self._create_parser()
477 self._create_parser()
471 self._parse_args(argv)
478 self._parse_args(argv)
472 self._convert_to_config()
479 self._convert_to_config()
473 return self.config
480 return self.config
474
481
475 def get_extra_args(self):
482 def get_extra_args(self):
476 if hasattr(self, 'extra_args'):
483 if hasattr(self, 'extra_args'):
477 return self.extra_args
484 return self.extra_args
478 else:
485 else:
479 return []
486 return []
480
487
481 def _create_parser(self):
488 def _create_parser(self):
482 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
489 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
483 self._add_arguments()
490 self._add_arguments()
484
491
485 def _add_arguments(self):
492 def _add_arguments(self):
486 raise NotImplementedError("subclasses must implement _add_arguments")
493 raise NotImplementedError("subclasses must implement _add_arguments")
487
494
488 def _parse_args(self, args):
495 def _parse_args(self, args):
489 """self.parser->self.parsed_data"""
496 """self.parser->self.parsed_data"""
490 # decode sys.argv to support unicode command-line options
497 # decode sys.argv to support unicode command-line options
491 uargs = []
498 uargs = []
492 for a in args:
499 for a in args:
493 if isinstance(a, str):
500 if isinstance(a, str):
494 # don't decode if we already got unicode
501 # don't decode if we already got unicode
495 a = a.decode(sys.stdin.encoding or
502 a = a.decode(sys.stdin.encoding or
496 sys.getdefaultencoding())
503 sys.getdefaultencoding())
497 uargs.append(a)
504 uargs.append(a)
498 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
505 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
499
506
500 def _convert_to_config(self):
507 def _convert_to_config(self):
501 """self.parsed_data->self.config"""
508 """self.parsed_data->self.config"""
502 for k, v in vars(self.parsed_data).iteritems():
509 for k, v in vars(self.parsed_data).iteritems():
503 exec_str = 'self.config.' + k + '= v'
510 exec_str = 'self.config.' + k + '= v'
504 exec exec_str in locals(), globals()
511 exec exec_str in locals(), globals()
505
512
506
513
@@ -1,129 +1,135 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 print app.config
120 app.init_bar()
119 app.init_bar()
121 self.assertEquals(app.bar.enabled, False)
120 self.assertEquals(app.bar.enabled, False)
122 self.assertEquals(app.bar.b, 5)
121 self.assertEquals(app.bar.b, 5)
123 app.parse_command_line(["--enable", "Bar.b=10"])
122 app.parse_command_line(["--enable", "Bar.b=10"])
124 print app.config
125 app.init_bar()
123 app.init_bar()
126 self.assertEquals(app.bar.enabled, True)
124 self.assertEquals(app.bar.enabled, True)
127 self.assertEquals(app.bar.b, 10)
125 self.assertEquals(app.bar.b, 10)
128
126
127 def test_extra_args(self):
128 app = MyApp()
129 app.parse_command_line(['extra', "Bar.b=5", "--disable", 'args'])
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
129
135
@@ -1,190 +1,197 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
27 from IPython.utils.traitlets import Int, Unicode
28 from IPython.config.configurable import Configurable
28 from IPython.config.configurable import Configurable
29 from IPython.config.loader import (
29 from IPython.config.loader import (
30 Config,
30 Config,
31 PyFileConfigLoader,
31 PyFileConfigLoader,
32 KeyValueConfigLoader,
32 KeyValueConfigLoader,
33 ArgParseConfigLoader,
33 ArgParseConfigLoader,
34 ConfigError
34 ConfigError
35 )
35 )
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Actual tests
38 # Actual tests
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41
41
42 pyfile = """
42 pyfile = """
43 c = get_config()
43 c = get_config()
44 c.a=10
44 c.a=10
45 c.b=20
45 c.b=20
46 c.Foo.Bar.value=10
46 c.Foo.Bar.value=10
47 c.Foo.Bam.value=range(10)
47 c.Foo.Bam.value=range(10)
48 c.D.C.value='hi there'
48 c.D.C.value='hi there'
49 """
49 """
50
50
51 class TestPyFileCL(TestCase):
51 class TestPyFileCL(TestCase):
52
52
53 def test_basic(self):
53 def test_basic(self):
54 fd, fname = mkstemp('.py')
54 fd, fname = mkstemp('.py')
55 f = os.fdopen(fd, 'w')
55 f = os.fdopen(fd, 'w')
56 f.write(pyfile)
56 f.write(pyfile)
57 f.close()
57 f.close()
58 # Unlink the file
58 # Unlink the file
59 cl = PyFileConfigLoader(fname)
59 cl = PyFileConfigLoader(fname)
60 config = cl.load_config()
60 config = cl.load_config()
61 self.assertEquals(config.a, 10)
61 self.assertEquals(config.a, 10)
62 self.assertEquals(config.b, 20)
62 self.assertEquals(config.b, 20)
63 self.assertEquals(config.Foo.Bar.value, 10)
63 self.assertEquals(config.Foo.Bar.value, 10)
64 self.assertEquals(config.Foo.Bam.value, range(10))
64 self.assertEquals(config.Foo.Bam.value, range(10))
65 self.assertEquals(config.D.C.value, 'hi there')
65 self.assertEquals(config.D.C.value, 'hi there')
66
66
67 class MyLoader1(ArgParseConfigLoader):
67 class MyLoader1(ArgParseConfigLoader):
68 def _add_arguments(self):
68 def _add_arguments(self):
69 p = self.parser
69 p = self.parser
70 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
70 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
71 p.add_argument('-b', dest='MyClass.bar', type=int)
71 p.add_argument('-b', dest='MyClass.bar', type=int)
72 p.add_argument('-n', dest='n', action='store_true')
72 p.add_argument('-n', dest='n', action='store_true')
73 p.add_argument('Global.bam', type=str)
73 p.add_argument('Global.bam', type=str)
74
74
75 class MyLoader2(ArgParseConfigLoader):
75 class MyLoader2(ArgParseConfigLoader):
76 def _add_arguments(self):
76 def _add_arguments(self):
77 subparsers = self.parser.add_subparsers(dest='subparser_name')
77 subparsers = self.parser.add_subparsers(dest='subparser_name')
78 subparser1 = subparsers.add_parser('1')
78 subparser1 = subparsers.add_parser('1')
79 subparser1.add_argument('-x',dest='Global.x')
79 subparser1.add_argument('-x',dest='Global.x')
80 subparser2 = subparsers.add_parser('2')
80 subparser2 = subparsers.add_parser('2')
81 subparser2.add_argument('y')
81 subparser2.add_argument('y')
82
82
83 class TestArgParseCL(TestCase):
83 class TestArgParseCL(TestCase):
84
84
85 def test_basic(self):
85 def test_basic(self):
86 cl = MyLoader1()
86 cl = MyLoader1()
87 config = cl.load_config('-f hi -b 10 -n wow'.split())
87 config = cl.load_config('-f hi -b 10 -n wow'.split())
88 self.assertEquals(config.Global.foo, 'hi')
88 self.assertEquals(config.Global.foo, 'hi')
89 self.assertEquals(config.MyClass.bar, 10)
89 self.assertEquals(config.MyClass.bar, 10)
90 self.assertEquals(config.n, True)
90 self.assertEquals(config.n, True)
91 self.assertEquals(config.Global.bam, 'wow')
91 self.assertEquals(config.Global.bam, 'wow')
92 config = cl.load_config(['wow'])
92 config = cl.load_config(['wow'])
93 self.assertEquals(config.keys(), ['Global'])
93 self.assertEquals(config.keys(), ['Global'])
94 self.assertEquals(config.Global.keys(), ['bam'])
94 self.assertEquals(config.Global.keys(), ['bam'])
95 self.assertEquals(config.Global.bam, 'wow')
95 self.assertEquals(config.Global.bam, 'wow')
96
96
97 def test_add_arguments(self):
97 def test_add_arguments(self):
98 cl = MyLoader2()
98 cl = MyLoader2()
99 config = cl.load_config('2 frobble'.split())
99 config = cl.load_config('2 frobble'.split())
100 self.assertEquals(config.subparser_name, '2')
100 self.assertEquals(config.subparser_name, '2')
101 self.assertEquals(config.y, 'frobble')
101 self.assertEquals(config.y, 'frobble')
102 config = cl.load_config('1 -x frobble'.split())
102 config = cl.load_config('1 -x frobble'.split())
103 self.assertEquals(config.subparser_name, '1')
103 self.assertEquals(config.subparser_name, '1')
104 self.assertEquals(config.Global.x, 'frobble')
104 self.assertEquals(config.Global.x, 'frobble')
105
105
106 def test_argv(self):
106 def test_argv(self):
107 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
107 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
108 config = cl.load_config()
108 config = cl.load_config()
109 self.assertEquals(config.Global.foo, 'hi')
109 self.assertEquals(config.Global.foo, 'hi')
110 self.assertEquals(config.MyClass.bar, 10)
110 self.assertEquals(config.MyClass.bar, 10)
111 self.assertEquals(config.n, True)
111 self.assertEquals(config.n, True)
112 self.assertEquals(config.Global.bam, 'wow')
112 self.assertEquals(config.Global.bam, 'wow')
113
113
114
114
115 class TestKeyValueCL(TestCase):
115 class TestKeyValueCL(TestCase):
116
116
117 def test_basic(self):
117 def test_basic(self):
118 cl = KeyValueConfigLoader()
118 cl = KeyValueConfigLoader()
119 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]]
120 config = cl.load_config(argv)
120 config = cl.load_config(argv)
121 self.assertEquals(config.a, 10)
121 self.assertEquals(config.a, 10)
122 self.assertEquals(config.b, 20)
122 self.assertEquals(config.b, 20)
123 self.assertEquals(config.Foo.Bar.value, 10)
123 self.assertEquals(config.Foo.Bar.value, 10)
124 self.assertEquals(config.Foo.Bam.value, range(10))
124 self.assertEquals(config.Foo.Bam.value, range(10))
125 self.assertEquals(config.D.C.value, 'hi there')
125 self.assertEquals(config.D.C.value, 'hi there')
126
127 def test_extra_args(self):
128 cl = KeyValueConfigLoader()
129 config = cl.load_config(['a=5', 'b', 'c=10', 'd'])
130 self.assertEquals(cl.extra_args, ['b', 'd'])
131 self.assertEquals(config.a, 5)
132 self.assertEquals(config.c, 10)
126
133
127
134
128 class TestConfig(TestCase):
135 class TestConfig(TestCase):
129
136
130 def test_setget(self):
137 def test_setget(self):
131 c = Config()
138 c = Config()
132 c.a = 10
139 c.a = 10
133 self.assertEquals(c.a, 10)
140 self.assertEquals(c.a, 10)
134 self.assertEquals(c.has_key('b'), False)
141 self.assertEquals(c.has_key('b'), False)
135
142
136 def test_auto_section(self):
143 def test_auto_section(self):
137 c = Config()
144 c = Config()
138 self.assertEquals(c.has_key('A'), True)
145 self.assertEquals(c.has_key('A'), True)
139 self.assertEquals(c._has_section('A'), False)
146 self.assertEquals(c._has_section('A'), False)
140 A = c.A
147 A = c.A
141 A.foo = 'hi there'
148 A.foo = 'hi there'
142 self.assertEquals(c._has_section('A'), True)
149 self.assertEquals(c._has_section('A'), True)
143 self.assertEquals(c.A.foo, 'hi there')
150 self.assertEquals(c.A.foo, 'hi there')
144 del c.A
151 del c.A
145 self.assertEquals(len(c.A.keys()),0)
152 self.assertEquals(len(c.A.keys()),0)
146
153
147 def test_merge_doesnt_exist(self):
154 def test_merge_doesnt_exist(self):
148 c1 = Config()
155 c1 = Config()
149 c2 = Config()
156 c2 = Config()
150 c2.bar = 10
157 c2.bar = 10
151 c2.Foo.bar = 10
158 c2.Foo.bar = 10
152 c1._merge(c2)
159 c1._merge(c2)
153 self.assertEquals(c1.Foo.bar, 10)
160 self.assertEquals(c1.Foo.bar, 10)
154 self.assertEquals(c1.bar, 10)
161 self.assertEquals(c1.bar, 10)
155 c2.Bar.bar = 10
162 c2.Bar.bar = 10
156 c1._merge(c2)
163 c1._merge(c2)
157 self.assertEquals(c1.Bar.bar, 10)
164 self.assertEquals(c1.Bar.bar, 10)
158
165
159 def test_merge_exists(self):
166 def test_merge_exists(self):
160 c1 = Config()
167 c1 = Config()
161 c2 = Config()
168 c2 = Config()
162 c1.Foo.bar = 10
169 c1.Foo.bar = 10
163 c1.Foo.bam = 30
170 c1.Foo.bam = 30
164 c2.Foo.bar = 20
171 c2.Foo.bar = 20
165 c2.Foo.wow = 40
172 c2.Foo.wow = 40
166 c1._merge(c2)
173 c1._merge(c2)
167 self.assertEquals(c1.Foo.bam, 30)
174 self.assertEquals(c1.Foo.bam, 30)
168 self.assertEquals(c1.Foo.bar, 20)
175 self.assertEquals(c1.Foo.bar, 20)
169 self.assertEquals(c1.Foo.wow, 40)
176 self.assertEquals(c1.Foo.wow, 40)
170 c2.Foo.Bam.bam = 10
177 c2.Foo.Bam.bam = 10
171 c1._merge(c2)
178 c1._merge(c2)
172 self.assertEquals(c1.Foo.Bam.bam, 10)
179 self.assertEquals(c1.Foo.Bam.bam, 10)
173
180
174 def test_deepcopy(self):
181 def test_deepcopy(self):
175 c1 = Config()
182 c1 = Config()
176 c1.Foo.bar = 10
183 c1.Foo.bar = 10
177 c1.Foo.bam = 30
184 c1.Foo.bam = 30
178 c1.a = 'asdf'
185 c1.a = 'asdf'
179 c1.b = range(10)
186 c1.b = range(10)
180 import copy
187 import copy
181 c2 = copy.deepcopy(c1)
188 c2 = copy.deepcopy(c1)
182 self.assertEquals(c1, c2)
189 self.assertEquals(c1, c2)
183 self.assert_(c1 is not c2)
190 self.assert_(c1 is not c2)
184 self.assert_(c1.Foo is not c2.Foo)
191 self.assert_(c1.Foo is not c2.Foo)
185
192
186 def test_builtin(self):
193 def test_builtin(self):
187 c1 = Config()
194 c1 = Config()
188 exec 'foo = True' in c1
195 exec 'foo = True' in c1
189 self.assertEquals(c1.foo, True)
196 self.assertEquals(c1.foo, True)
190 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
197 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
General Comments 0
You need to be logged in to leave comments. Login now