##// END OF EJS Templates
prevent flags from clobbering entire config sections...
MinRK -
Show More
@@ -1,362 +1,360 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
115
116 def __init__(self, **kwargs):
116 def __init__(self, **kwargs):
117 SingletonConfigurable.__init__(self, **kwargs)
117 SingletonConfigurable.__init__(self, **kwargs)
118 # Add my class to self.classes so my attributes appear in command line
118 # Add my class to self.classes so my attributes appear in command line
119 # options.
119 # options.
120 self.classes.insert(0, self.__class__)
120 self.classes.insert(0, self.__class__)
121
121
122 # ensure self.flags dict is valid
122 # ensure self.flags dict is valid
123 for key,value in self.flags.iteritems():
123 for key,value in self.flags.iteritems():
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
127 self.init_logging()
127 self.init_logging()
128
128
129 def _config_changed(self, name, old, new):
129 def _config_changed(self, name, old, new):
130 SingletonConfigurable._config_changed(self, name, old, new)
130 SingletonConfigurable._config_changed(self, name, old, new)
131 self.log.debug('Config changed:')
131 self.log.debug('Config changed:')
132 self.log.debug(repr(new))
132 self.log.debug(repr(new))
133
133
134 def init_logging(self):
134 def init_logging(self):
135 """Start logging for this application.
135 """Start logging for this application.
136
136
137 The default is to log to stdout using a StreaHandler. The log level
137 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
138 starts at loggin.WARN, but this can be adjusted by setting the
139 ``log_level`` attribute.
139 ``log_level`` attribute.
140 """
140 """
141 self.log = logging.getLogger(self.__class__.__name__)
141 self.log = logging.getLogger(self.__class__.__name__)
142 self.log.setLevel(self.log_level)
142 self.log.setLevel(self.log_level)
143 self._log_handler = logging.StreamHandler()
143 self._log_handler = logging.StreamHandler()
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 self._log_handler.setFormatter(self._log_formatter)
145 self._log_handler.setFormatter(self._log_formatter)
146 self.log.addHandler(self._log_handler)
146 self.log.addHandler(self._log_handler)
147
147
148 def initialize(self, argv=None):
148 def initialize(self, argv=None):
149 """Do the basic steps to configure me.
149 """Do the basic steps to configure me.
150
150
151 Override in subclasses.
151 Override in subclasses.
152 """
152 """
153 self.parse_command_line(argv)
153 self.parse_command_line(argv)
154
154
155
155
156 def start(self):
156 def start(self):
157 """Start the app mainloop.
157 """Start the app mainloop.
158
158
159 Override in subclasses.
159 Override in subclasses.
160 """
160 """
161 if self.subapp is not None:
161 if self.subapp is not None:
162 return self.subapp.start()
162 return self.subapp.start()
163
163
164 def _log_level_changed(self, name, old, new):
164 def _log_level_changed(self, name, old, new):
165 """Adjust the log level when log_level is set."""
165 """Adjust the log level when log_level is set."""
166 self.log.setLevel(new)
166 self.log.setLevel(new)
167
167
168 def print_alias_help(self):
168 def print_alias_help(self):
169 """print the alias part of the help"""
169 """print the alias part of the help"""
170 if not self.aliases:
170 if not self.aliases:
171 return
171 return
172
172
173 lines = ['Aliases']
173 lines = ['Aliases']
174 lines.append('-'*len(lines[0]))
174 lines.append('-'*len(lines[0]))
175 lines.append(self.alias_description)
175 lines.append(self.alias_description)
176 lines.append('')
176 lines.append('')
177
177
178 classdict = {}
178 classdict = {}
179 for cls in self.classes:
179 for cls in self.classes:
180 # include all parents (up to, but excluding Configurable) in available names
180 # include all parents (up to, but excluding Configurable) in available names
181 for c in cls.mro()[:-3]:
181 for c in cls.mro()[:-3]:
182 classdict[c.__name__] = c
182 classdict[c.__name__] = c
183
183
184 for alias, longname in self.aliases.iteritems():
184 for alias, longname in self.aliases.iteritems():
185 classname, traitname = longname.split('.',1)
185 classname, traitname = longname.split('.',1)
186 cls = classdict[classname]
186 cls = classdict[classname]
187
187
188 trait = cls.class_traits(config=True)[traitname]
188 trait = cls.class_traits(config=True)[traitname]
189 help = cls.class_get_trait_help(trait)
189 help = cls.class_get_trait_help(trait)
190 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
190 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
191 lines.append(help)
191 lines.append(help)
192 lines.append('')
192 lines.append('')
193 print '\n'.join(lines)
193 print '\n'.join(lines)
194
194
195 def print_flag_help(self):
195 def print_flag_help(self):
196 """print the flag part of the help"""
196 """print the flag part of the help"""
197 if not self.flags:
197 if not self.flags:
198 return
198 return
199
199
200 lines = ['Flags']
200 lines = ['Flags']
201 lines.append('-'*len(lines[0]))
201 lines.append('-'*len(lines[0]))
202 lines.append(self.flag_description)
202 lines.append(self.flag_description)
203 lines.append('')
203 lines.append('')
204
204
205 for m, (cfg,help) in self.flags.iteritems():
205 for m, (cfg,help) in self.flags.iteritems():
206 lines.append('--'+m)
206 lines.append('--'+m)
207 lines.append(indent(help, flatten=True))
207 lines.append(indent(help, flatten=True))
208 lines.append('')
208 lines.append('')
209 print '\n'.join(lines)
209 print '\n'.join(lines)
210
210
211 def print_subcommands(self):
211 def print_subcommands(self):
212 """print the subcommand part of the help"""
212 """print the subcommand part of the help"""
213 if not self.subcommands:
213 if not self.subcommands:
214 return
214 return
215
215
216 lines = ["Subcommands"]
216 lines = ["Subcommands"]
217 lines.append('-'*len(lines[0]))
217 lines.append('-'*len(lines[0]))
218 for subc, (cls,help) in self.subcommands.iteritems():
218 for subc, (cls,help) in self.subcommands.iteritems():
219 lines.append("%s : %s"%(subc, cls))
219 lines.append("%s : %s"%(subc, cls))
220 if help:
220 if help:
221 lines.append(indent(help, flatten=True))
221 lines.append(indent(help, flatten=True))
222 lines.append('')
222 lines.append('')
223 print '\n'.join(lines)
223 print '\n'.join(lines)
224
224
225 def print_help(self, classes=False):
225 def print_help(self, classes=False):
226 """Print the help for each Configurable class in self.classes.
226 """Print the help for each Configurable class in self.classes.
227
227
228 If classes=False (the default), only flags and aliases are printed
228 If classes=False (the default), only flags and aliases are printed
229 """
229 """
230 self.print_subcommands()
230 self.print_subcommands()
231 self.print_flag_help()
231 self.print_flag_help()
232 self.print_alias_help()
232 self.print_alias_help()
233
233
234 if classes:
234 if classes:
235 if self.classes:
235 if self.classes:
236 print "Class parameters"
236 print "Class parameters"
237 print "----------------"
237 print "----------------"
238 print self.keyvalue_description
238 print self.keyvalue_description
239 print
239 print
240
240
241 for cls in self.classes:
241 for cls in self.classes:
242 cls.class_print_help()
242 cls.class_print_help()
243 print
243 print
244 else:
244 else:
245 print "To see all available configurables, use `--help-all`"
245 print "To see all available configurables, use `--help-all`"
246 print
246 print
247
247
248 def print_description(self):
248 def print_description(self):
249 """Print the application description."""
249 """Print the application description."""
250 print self.description
250 print self.description
251 print
251 print
252
252
253 def print_version(self):
253 def print_version(self):
254 """Print the version string."""
254 """Print the version string."""
255 print self.version
255 print self.version
256
256
257 def update_config(self, config):
257 def update_config(self, config):
258 """Fire the traits events when the config is updated."""
258 """Fire the traits events when the config is updated."""
259 # Save a copy of the current config.
259 # Save a copy of the current config.
260 newconfig = deepcopy(self.config)
260 newconfig = deepcopy(self.config)
261 # Merge the new config into the current one.
261 # Merge the new config into the current one.
262 newconfig._merge(config)
262 newconfig._merge(config)
263 # Save the combined config as self.config, which triggers the traits
263 # Save the combined config as self.config, which triggers the traits
264 # events.
264 # events.
265 self.config = newconfig
265 self.config = newconfig
266
266
267 def initialize_subcommand(self, subc, argv=None):
267 def initialize_subcommand(self, subc, argv=None):
268 """Initialize a subcommand with argv"""
268 """Initialize a subcommand with argv"""
269 subapp,help = self.subcommands.get(subc, (None,None))
269 subapp,help = self.subcommands.get(subc, (None,None))
270 if subapp is None:
270 if subapp is None:
271 self.print_description()
271 self.print_description()
272 print "No such subcommand: %r"%subc
272 print "No such subcommand: %r"%subc
273 print
273 print
274 self.print_subcommands()
274 self.print_subcommands()
275 self.exit(1)
275 self.exit(1)
276
276
277 if isinstance(subapp, basestring):
277 if isinstance(subapp, basestring):
278 subapp = import_item(subapp)
278 subapp = import_item(subapp)
279
279
280 # instantiate
280 # instantiate
281 self.subapp = subapp()
281 self.subapp = subapp()
282 # and initialize subapp
282 # and initialize subapp
283 self.subapp.initialize(argv)
283 self.subapp.initialize(argv)
284
284
285 def parse_command_line(self, argv=None):
285 def parse_command_line(self, argv=None):
286 """Parse the command line arguments."""
286 """Parse the command line arguments."""
287 argv = sys.argv[1:] if argv is None else argv
287 argv = sys.argv[1:] if argv is None else argv
288
288
289 if self.subcommands and len(argv) > 0:
289 if self.subcommands and len(argv) > 0:
290 # we have subcommands, and one may have been specified
290 # we have subcommands, and one may have been specified
291 subc, subargv = argv[0], argv[1:]
291 subc, subargv = argv[0], argv[1:]
292 if re.match(r'^\w(\-?\w)*$', subc):
292 if re.match(r'^\w(\-?\w)*$', subc):
293 # it's a subcommand, and *not* a flag or class parameter
293 # it's a subcommand, and *not* a flag or class parameter
294 return self.initialize_subcommand(subc, subargv)
294 return self.initialize_subcommand(subc, subargv)
295
295
296 if '-h' in argv or '--help' in argv or '--help-all' in argv:
296 if '-h' in argv or '--help' in argv or '--help-all' in argv:
297 self.print_description()
297 self.print_description()
298 self.print_help('--help-all' in argv)
298 self.print_help('--help-all' in argv)
299 self.exit(0)
299 self.exit(0)
300
300
301 if '--version' in argv:
301 if '--version' in argv:
302 self.print_version()
302 self.print_version()
303 self.exit(0)
303 self.exit(0)
304
304
305 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
305 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
306 flags=self.flags)
306 flags=self.flags)
307 try:
307 try:
308 config = loader.load_config()
308 config = loader.load_config()
309 except ArgumentError as e:
309 except ArgumentError as e:
310 self.log.fatal(str(e))
310 self.log.fatal(str(e))
311 self.print_description()
311 self.print_description()
312 self.print_help()
312 self.print_help()
313 self.exit(1)
313 self.exit(1)
314 self.update_config(config)
314 self.update_config(config)
315
315
316 def load_config_file(self, filename, path=None):
316 def load_config_file(self, filename, path=None):
317 """Load a .py based config file by filename and path."""
317 """Load a .py based config file by filename and path."""
318 loader = PyFileConfigLoader(filename, path=path)
318 loader = PyFileConfigLoader(filename, path=path)
319 config = loader.load_config()
319 config = loader.load_config()
320 self.update_config(config)
320 self.update_config(config)
321
321
322 def exit(self, exit_status=0):
322 def exit(self, exit_status=0):
323 self.log.debug("Exiting application: %s" % self.name)
323 self.log.debug("Exiting application: %s" % self.name)
324 sys.exit(exit_status)
324 sys.exit(exit_status)
325
325
326 #-----------------------------------------------------------------------------
326 #-----------------------------------------------------------------------------
327 # utility functions, for convenience
327 # utility functions, for convenience
328 #-----------------------------------------------------------------------------
328 #-----------------------------------------------------------------------------
329
329
330 def boolean_flag(name, configurable, set_help='', unset_help=''):
330 def boolean_flag(name, configurable, set_help='', unset_help=''):
331 """helper for building basic --trait, --no-trait flags
331 """helper for building basic --trait, --no-trait flags
332
332
333 Parameters
333 Parameters
334 ----------
334 ----------
335
335
336 name : str
336 name : str
337 The name of the flag.
337 The name of the flag.
338 configurable : str
338 configurable : str
339 The 'Class.trait' string of the trait to be set/unset with the flag
339 The 'Class.trait' string of the trait to be set/unset with the flag
340 set_help : unicode
340 set_help : unicode
341 help string for --name flag
341 help string for --name flag
342 unset_help : unicode
342 unset_help : unicode
343 help string for --no-name flag
343 help string for --no-name flag
344
344
345 Returns
345 Returns
346 -------
346 -------
347
347
348 cfg : dict
348 cfg : dict
349 A dict with two keys: 'name', and 'no-name', for setting and unsetting
349 A dict with two keys: 'name', and 'no-name', for setting and unsetting
350 the trait, respectively.
350 the trait, respectively.
351 """
351 """
352 # default helpstrings
352 # default helpstrings
353 set_help = set_help or "set %s=True"%configurable
353 set_help = set_help or "set %s=True"%configurable
354 unset_help = unset_help or "set %s=False"%configurable
354 unset_help = unset_help or "set %s=False"%configurable
355
355
356 cls,trait = configurable.split('.')
356 cls,trait = configurable.split('.')
357
357
358 setter = Config()
358 setter = {cls : {trait : True}}
359 setter[cls][trait] = True
359 unsetter = {cls : {trait : False}}
360 unsetter = Config()
361 unsetter[cls][trait] = False
362 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
360 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
@@ -1,504 +1,506 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 for item in argv:
386 for item in argv:
387 if kv_pattern.match(item):
387 if kv_pattern.match(item):
388 lhs,rhs = item.split('=',1)
388 lhs,rhs = item.split('=',1)
389 # Substitute longnames for aliases.
389 # Substitute longnames for aliases.
390 if lhs in aliases:
390 if lhs in aliases:
391 lhs = aliases[lhs]
391 lhs = aliases[lhs]
392 exec_str = 'self.config.' + lhs + '=' + rhs
392 exec_str = 'self.config.' + lhs + '=' + rhs
393 try:
393 try:
394 # Try to see if regular Python syntax will work. This
394 # Try to see if regular Python syntax will work. This
395 # won't handle strings as the quote marks are removed
395 # won't handle strings as the quote marks are removed
396 # by the system shell.
396 # by the system shell.
397 exec exec_str in locals(), globals()
397 exec exec_str in locals(), globals()
398 except (NameError, SyntaxError):
398 except (NameError, SyntaxError):
399 # This case happens if the rhs is a string but without
399 # This case happens if the rhs is a string but without
400 # the quote marks. We add the quote marks and see if
400 # the quote marks. We add the quote marks and see if
401 # it succeeds. If it still fails, we let it raise.
401 # it succeeds. If it still fails, we let it raise.
402 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
402 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
403 exec exec_str in locals(), globals()
403 exec exec_str in locals(), globals()
404 elif flag_pattern.match(item):
404 elif flag_pattern.match(item):
405 # trim leading '--'
405 # trim leading '--'
406 m = item[2:]
406 m = item[2:]
407 cfg,_ = flags.get(m, (None,None))
407 cfg,_ = flags.get(m, (None,None))
408 if cfg is None:
408 if cfg is None:
409 raise ArgumentError("Unrecognized flag: %r"%item)
409 raise ArgumentError("Unrecognized flag: %r"%item)
410 elif isinstance(cfg, (dict, Config)):
410 elif isinstance(cfg, (dict, Config)):
411 # update self.config with Config:
411 # don't clobber whole config sections, update
412 self.config.update(cfg)
412 # each section from config:
413 for sec,c in cfg.iteritems():
414 self.config[sec].update(c)
413 else:
415 else:
414 raise ValueError("Invalid flag: %r"%flag)
416 raise ValueError("Invalid flag: %r"%flag)
415 else:
417 else:
416 raise ArgumentError("Invalid argument: %r"%item)
418 raise ArgumentError("Invalid argument: %r"%item)
417 return self.config
419 return self.config
418
420
419 class ArgParseConfigLoader(CommandLineConfigLoader):
421 class ArgParseConfigLoader(CommandLineConfigLoader):
420 """A loader that uses the argparse module to load from the command line."""
422 """A loader that uses the argparse module to load from the command line."""
421
423
422 def __init__(self, argv=None, *parser_args, **parser_kw):
424 def __init__(self, argv=None, *parser_args, **parser_kw):
423 """Create a config loader for use with argparse.
425 """Create a config loader for use with argparse.
424
426
425 Parameters
427 Parameters
426 ----------
428 ----------
427
429
428 argv : optional, list
430 argv : optional, list
429 If given, used to read command-line arguments from, otherwise
431 If given, used to read command-line arguments from, otherwise
430 sys.argv[1:] is used.
432 sys.argv[1:] is used.
431
433
432 parser_args : tuple
434 parser_args : tuple
433 A tuple of positional arguments that will be passed to the
435 A tuple of positional arguments that will be passed to the
434 constructor of :class:`argparse.ArgumentParser`.
436 constructor of :class:`argparse.ArgumentParser`.
435
437
436 parser_kw : dict
438 parser_kw : dict
437 A tuple of keyword arguments that will be passed to the
439 A tuple of keyword arguments that will be passed to the
438 constructor of :class:`argparse.ArgumentParser`.
440 constructor of :class:`argparse.ArgumentParser`.
439
441
440 Returns
442 Returns
441 -------
443 -------
442 config : Config
444 config : Config
443 The resulting Config object.
445 The resulting Config object.
444 """
446 """
445 super(CommandLineConfigLoader, self).__init__()
447 super(CommandLineConfigLoader, self).__init__()
446 if argv == None:
448 if argv == None:
447 argv = sys.argv[1:]
449 argv = sys.argv[1:]
448 self.argv = argv
450 self.argv = argv
449 self.parser_args = parser_args
451 self.parser_args = parser_args
450 self.version = parser_kw.pop("version", None)
452 self.version = parser_kw.pop("version", None)
451 kwargs = dict(argument_default=argparse.SUPPRESS)
453 kwargs = dict(argument_default=argparse.SUPPRESS)
452 kwargs.update(parser_kw)
454 kwargs.update(parser_kw)
453 self.parser_kw = kwargs
455 self.parser_kw = kwargs
454
456
455 def load_config(self, argv=None):
457 def load_config(self, argv=None):
456 """Parse command line arguments and return as a Config object.
458 """Parse command line arguments and return as a Config object.
457
459
458 Parameters
460 Parameters
459 ----------
461 ----------
460
462
461 args : optional, list
463 args : optional, list
462 If given, a list with the structure of sys.argv[1:] to parse
464 If given, a list with the structure of sys.argv[1:] to parse
463 arguments from. If not given, the instance's self.argv attribute
465 arguments from. If not given, the instance's self.argv attribute
464 (given at construction time) is used."""
466 (given at construction time) is used."""
465 self.clear()
467 self.clear()
466 if argv is None:
468 if argv is None:
467 argv = self.argv
469 argv = self.argv
468 self._create_parser()
470 self._create_parser()
469 self._parse_args(argv)
471 self._parse_args(argv)
470 self._convert_to_config()
472 self._convert_to_config()
471 return self.config
473 return self.config
472
474
473 def get_extra_args(self):
475 def get_extra_args(self):
474 if hasattr(self, 'extra_args'):
476 if hasattr(self, 'extra_args'):
475 return self.extra_args
477 return self.extra_args
476 else:
478 else:
477 return []
479 return []
478
480
479 def _create_parser(self):
481 def _create_parser(self):
480 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
482 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
481 self._add_arguments()
483 self._add_arguments()
482
484
483 def _add_arguments(self):
485 def _add_arguments(self):
484 raise NotImplementedError("subclasses must implement _add_arguments")
486 raise NotImplementedError("subclasses must implement _add_arguments")
485
487
486 def _parse_args(self, args):
488 def _parse_args(self, args):
487 """self.parser->self.parsed_data"""
489 """self.parser->self.parsed_data"""
488 # decode sys.argv to support unicode command-line options
490 # decode sys.argv to support unicode command-line options
489 uargs = []
491 uargs = []
490 for a in args:
492 for a in args:
491 if isinstance(a, str):
493 if isinstance(a, str):
492 # don't decode if we already got unicode
494 # don't decode if we already got unicode
493 a = a.decode(sys.stdin.encoding or
495 a = a.decode(sys.stdin.encoding or
494 sys.getdefaultencoding())
496 sys.getdefaultencoding())
495 uargs.append(a)
497 uargs.append(a)
496 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
498 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
497
499
498 def _convert_to_config(self):
500 def _convert_to_config(self):
499 """self.parsed_data->self.config"""
501 """self.parsed_data->self.config"""
500 for k, v in vars(self.parsed_data).iteritems():
502 for k, v in vars(self.parsed_data).iteritems():
501 exec_str = 'self.config.' + k + '= v'
503 exec_str = 'self.config.' + k + '= v'
502 exec exec_str in locals(), globals()
504 exec exec_str in locals(), globals()
503
505
504
506
@@ -1,105 +1,129 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 enabled = Bool(True, config=True, help="Enable bar.")
46 enabled = Bool(True, config=True, help="Enable bar.")
46
47
47
48
48 class MyApp(Application):
49 class MyApp(Application):
49
50
50 name = Unicode(u'myapp')
51 name = Unicode(u'myapp')
51 running = Bool(False, config=True,
52 running = Bool(False, config=True,
52 help="Is the app running?")
53 help="Is the app running?")
53 classes = List([Bar, Foo])
54 classes = List([Bar, Foo])
54 config_file = Unicode(u'', config=True,
55 config_file = Unicode(u'', config=True,
55 help="Load this config file")
56 help="Load this config file")
56
57
57 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',
58 enabled='Bar.enabled', log_level='MyApp.log_level'))
59 enabled='Bar.enabled', log_level='MyApp.log_level'))
59
60
60 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"),
61 disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False")))
62 disable=({'Bar': {'enabled' : False}}, "Set Bar.enabled to False")))
62
63
63 def init_foo(self):
64 def init_foo(self):
64 self.foo = Foo(config=self.config)
65 self.foo = Foo(config=self.config)
65
66
66 def init_bar(self):
67 def init_bar(self):
67 self.bar = Bar(config=self.config)
68 self.bar = Bar(config=self.config)
68
69
69
70
70 class TestApplication(TestCase):
71 class TestApplication(TestCase):
71
72
72 def test_basic(self):
73 def test_basic(self):
73 app = MyApp()
74 app = MyApp()
74 self.assertEquals(app.name, u'myapp')
75 self.assertEquals(app.name, u'myapp')
75 self.assertEquals(app.running, False)
76 self.assertEquals(app.running, False)
76 self.assertEquals(app.classes, [MyApp,Bar,Foo])
77 self.assertEquals(app.classes, [MyApp,Bar,Foo])
77 self.assertEquals(app.config_file, u'')
78 self.assertEquals(app.config_file, u'')
78
79
79 def test_config(self):
80 def test_config(self):
80 app = MyApp()
81 app = MyApp()
81 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"])
82 config = app.config
83 config = app.config
83 self.assertEquals(config.Foo.i, 10)
84 self.assertEquals(config.Foo.i, 10)
84 self.assertEquals(config.Foo.j, 10)
85 self.assertEquals(config.Foo.j, 10)
85 self.assertEquals(config.Bar.enabled, False)
86 self.assertEquals(config.Bar.enabled, False)
86 self.assertEquals(config.MyApp.log_level,50)
87 self.assertEquals(config.MyApp.log_level,50)
87
88
88 def test_config_propagation(self):
89 def test_config_propagation(self):
89 app = MyApp()
90 app = MyApp()
90 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"])
91 app.init_foo()
92 app.init_foo()
92 app.init_bar()
93 app.init_bar()
93 self.assertEquals(app.foo.i, 10)
94 self.assertEquals(app.foo.i, 10)
94 self.assertEquals(app.foo.j, 10)
95 self.assertEquals(app.foo.j, 10)
95 self.assertEquals(app.bar.enabled, False)
96 self.assertEquals(app.bar.enabled, False)
96
97
97 def test_alias(self):
98 def test_flags(self):
98 app = MyApp()
99 app = MyApp()
99 app.parse_command_line(["--disable"])
100 app.parse_command_line(["--disable"])
100 app.init_bar()
101 app.init_bar()
101 self.assertEquals(app.bar.enabled, False)
102 self.assertEquals(app.bar.enabled, False)
102 app.parse_command_line(["--enable"])
103 app.parse_command_line(["--enable"])
103 app.init_bar()
104 app.init_bar()
104 self.assertEquals(app.bar.enabled, True)
105 self.assertEquals(app.bar.enabled, True)
105
106
107 def test_aliases(self):
108 app = MyApp()
109 app.parse_command_line(["i=5", "j=10"])
110 app.init_foo()
111 self.assertEquals(app.foo.i, 5)
112 app.init_foo()
113 self.assertEquals(app.foo.j, 10)
114
115 def test_flag_clobber(self):
116 """test that setting flags doesn't clobber existing settings"""
117 app = MyApp()
118 app.parse_command_line(["Bar.b=5", "--disable"])
119 print app.config
120 app.init_bar()
121 self.assertEquals(app.bar.enabled, False)
122 self.assertEquals(app.bar.b, 5)
123 app.parse_command_line(["--enable", "Bar.b=10"])
124 print app.config
125 app.init_bar()
126 self.assertEquals(app.bar.enabled, True)
127 self.assertEquals(app.bar.b, 10)
128
129
@@ -1,435 +1,435 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15
15
16 Notes
16 Notes
17 -----
17 -----
18 """
18 """
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Copyright (C) 2008-2009 The IPython Development Team
21 # Copyright (C) 2008-2009 The IPython Development Team
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Imports
28 # Imports
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 import logging
31 import logging
32 import os
32 import os
33 import shutil
33 import shutil
34 import sys
34 import sys
35
35
36 from IPython.config.application import Application
36 from IPython.config.application import Application
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.config.loader import Config
38 from IPython.config.loader import Config
39 from IPython.core import release, crashhandler
39 from IPython.core import release, crashhandler
40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, expand_path
40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, expand_path
41 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
41 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Classes and functions
44 # Classes and functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Module errors
49 # Module errors
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class ProfileDirError(Exception):
52 class ProfileDirError(Exception):
53 pass
53 pass
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Class for managing profile directories
57 # Class for managing profile directories
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class ProfileDir(Configurable):
60 class ProfileDir(Configurable):
61 """An object to manage the profile directory and its resources.
61 """An object to manage the profile directory and its resources.
62
62
63 The profile directory is used by all IPython applications, to manage
63 The profile directory is used by all IPython applications, to manage
64 configuration, logging and security.
64 configuration, logging and security.
65
65
66 This object knows how to find, create and manage these directories. This
66 This object knows how to find, create and manage these directories. This
67 should be used by any code that wants to handle profiles.
67 should be used by any code that wants to handle profiles.
68 """
68 """
69
69
70 security_dir_name = Unicode('security')
70 security_dir_name = Unicode('security')
71 log_dir_name = Unicode('log')
71 log_dir_name = Unicode('log')
72 pid_dir_name = Unicode('pid')
72 pid_dir_name = Unicode('pid')
73 security_dir = Unicode(u'')
73 security_dir = Unicode(u'')
74 log_dir = Unicode(u'')
74 log_dir = Unicode(u'')
75 pid_dir = Unicode(u'')
75 pid_dir = Unicode(u'')
76
76
77 location = Unicode(u'', config=True,
77 location = Unicode(u'', config=True,
78 help="""Set the profile location directly. This overrides the logic used by the
78 help="""Set the profile location directly. This overrides the logic used by the
79 `profile` option.""",
79 `profile` option.""",
80 )
80 )
81
81
82 _location_isset = Bool(False) # flag for detecting multiply set location
82 _location_isset = Bool(False) # flag for detecting multiply set location
83
83
84 def _location_changed(self, name, old, new):
84 def _location_changed(self, name, old, new):
85 if self._location_isset:
85 if self._location_isset:
86 raise RuntimeError("Cannot set profile location more than once.")
86 raise RuntimeError("Cannot set profile location more than once.")
87 self._location_isset = True
87 self._location_isset = True
88 if not os.path.isdir(new):
88 if not os.path.isdir(new):
89 os.makedirs(new)
89 os.makedirs(new)
90
90
91 # ensure config files exist:
91 # ensure config files exist:
92 self.security_dir = os.path.join(new, self.security_dir_name)
92 self.security_dir = os.path.join(new, self.security_dir_name)
93 self.log_dir = os.path.join(new, self.log_dir_name)
93 self.log_dir = os.path.join(new, self.log_dir_name)
94 self.pid_dir = os.path.join(new, self.pid_dir_name)
94 self.pid_dir = os.path.join(new, self.pid_dir_name)
95 self.check_dirs()
95 self.check_dirs()
96
96
97 def _log_dir_changed(self, name, old, new):
97 def _log_dir_changed(self, name, old, new):
98 self.check_log_dir()
98 self.check_log_dir()
99
99
100 def check_log_dir(self):
100 def check_log_dir(self):
101 if not os.path.isdir(self.log_dir):
101 if not os.path.isdir(self.log_dir):
102 os.mkdir(self.log_dir)
102 os.mkdir(self.log_dir)
103
103
104 def _security_dir_changed(self, name, old, new):
104 def _security_dir_changed(self, name, old, new):
105 self.check_security_dir()
105 self.check_security_dir()
106
106
107 def check_security_dir(self):
107 def check_security_dir(self):
108 if not os.path.isdir(self.security_dir):
108 if not os.path.isdir(self.security_dir):
109 os.mkdir(self.security_dir, 0700)
109 os.mkdir(self.security_dir, 0700)
110 else:
110 else:
111 os.chmod(self.security_dir, 0700)
111 os.chmod(self.security_dir, 0700)
112
112
113 def _pid_dir_changed(self, name, old, new):
113 def _pid_dir_changed(self, name, old, new):
114 self.check_pid_dir()
114 self.check_pid_dir()
115
115
116 def check_pid_dir(self):
116 def check_pid_dir(self):
117 if not os.path.isdir(self.pid_dir):
117 if not os.path.isdir(self.pid_dir):
118 os.mkdir(self.pid_dir, 0700)
118 os.mkdir(self.pid_dir, 0700)
119 else:
119 else:
120 os.chmod(self.pid_dir, 0700)
120 os.chmod(self.pid_dir, 0700)
121
121
122 def check_dirs(self):
122 def check_dirs(self):
123 self.check_security_dir()
123 self.check_security_dir()
124 self.check_log_dir()
124 self.check_log_dir()
125 self.check_pid_dir()
125 self.check_pid_dir()
126
126
127 def copy_config_file(self, config_file, path=None, overwrite=False):
127 def copy_config_file(self, config_file, path=None, overwrite=False):
128 """Copy a default config file into the active profile directory.
128 """Copy a default config file into the active profile directory.
129
129
130 Default configuration files are kept in :mod:`IPython.config.default`.
130 Default configuration files are kept in :mod:`IPython.config.default`.
131 This function moves these from that location to the working profile
131 This function moves these from that location to the working profile
132 directory.
132 directory.
133 """
133 """
134 dst = os.path.join(self.location, config_file)
134 dst = os.path.join(self.location, config_file)
135 if os.path.isfile(dst) and not overwrite:
135 if os.path.isfile(dst) and not overwrite:
136 return
136 return
137 if path is None:
137 if path is None:
138 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
138 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
139 src = os.path.join(path, config_file)
139 src = os.path.join(path, config_file)
140 shutil.copy(src, dst)
140 shutil.copy(src, dst)
141
141
142 @classmethod
142 @classmethod
143 def create_profile_dir(cls, profile_dir, config=None):
143 def create_profile_dir(cls, profile_dir, config=None):
144 """Create a new profile directory given a full path.
144 """Create a new profile directory given a full path.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 profile_dir : str
148 profile_dir : str
149 The full path to the profile directory. If it does exist, it will
149 The full path to the profile directory. If it does exist, it will
150 be used. If not, it will be created.
150 be used. If not, it will be created.
151 """
151 """
152 return cls(location=profile_dir, config=config)
152 return cls(location=profile_dir, config=config)
153
153
154 @classmethod
154 @classmethod
155 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
155 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
156 """Create a profile dir by profile name and path.
156 """Create a profile dir by profile name and path.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 path : unicode
160 path : unicode
161 The path (directory) to put the profile directory in.
161 The path (directory) to put the profile directory in.
162 name : unicode
162 name : unicode
163 The name of the profile. The name of the profile directory will
163 The name of the profile. The name of the profile directory will
164 be "profile_<profile>".
164 be "profile_<profile>".
165 """
165 """
166 if not os.path.isdir(path):
166 if not os.path.isdir(path):
167 raise ProfileDirError('Directory not found: %s' % path)
167 raise ProfileDirError('Directory not found: %s' % path)
168 profile_dir = os.path.join(path, u'profile_' + name)
168 profile_dir = os.path.join(path, u'profile_' + name)
169 return cls(location=profile_dir, config=config)
169 return cls(location=profile_dir, config=config)
170
170
171 @classmethod
171 @classmethod
172 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
172 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
173 """Find an existing profile dir by profile name, return its ProfileDir.
173 """Find an existing profile dir by profile name, return its ProfileDir.
174
174
175 This searches through a sequence of paths for a profile dir. If it
175 This searches through a sequence of paths for a profile dir. If it
176 is not found, a :class:`ProfileDirError` exception will be raised.
176 is not found, a :class:`ProfileDirError` exception will be raised.
177
177
178 The search path algorithm is:
178 The search path algorithm is:
179 1. ``os.getcwd()``
179 1. ``os.getcwd()``
180 2. ``ipython_dir``
180 2. ``ipython_dir``
181 3. The directories found in the ":" separated
181 3. The directories found in the ":" separated
182 :env:`IPCLUSTER_DIR_PATH` environment variable.
182 :env:`IPCLUSTER_DIR_PATH` environment variable.
183
183
184 Parameters
184 Parameters
185 ----------
185 ----------
186 ipython_dir : unicode or str
186 ipython_dir : unicode or str
187 The IPython directory to use.
187 The IPython directory to use.
188 name : unicode or str
188 name : unicode or str
189 The name of the profile. The name of the profile directory
189 The name of the profile. The name of the profile directory
190 will be "profile_<profile>".
190 will be "profile_<profile>".
191 """
191 """
192 dirname = u'profile_' + name
192 dirname = u'profile_' + name
193 profile_dir_paths = os.environ.get('IPYTHON_PROFILE_PATH','')
193 profile_dir_paths = os.environ.get('IPYTHON_PROFILE_PATH','')
194 if profile_dir_paths:
194 if profile_dir_paths:
195 profile_dir_paths = profile_dir_paths.split(os.pathsep)
195 profile_dir_paths = profile_dir_paths.split(os.pathsep)
196 else:
196 else:
197 profile_dir_paths = []
197 profile_dir_paths = []
198 paths = [os.getcwd(), ipython_dir] + profile_dir_paths
198 paths = [os.getcwd(), ipython_dir] + profile_dir_paths
199 for p in paths:
199 for p in paths:
200 profile_dir = os.path.join(p, dirname)
200 profile_dir = os.path.join(p, dirname)
201 if os.path.isdir(profile_dir):
201 if os.path.isdir(profile_dir):
202 return cls(location=profile_dir, config=config)
202 return cls(location=profile_dir, config=config)
203 else:
203 else:
204 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
204 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
205
205
206 @classmethod
206 @classmethod
207 def find_profile_dir(cls, profile_dir, config=None):
207 def find_profile_dir(cls, profile_dir, config=None):
208 """Find/create a profile dir and return its ProfileDir.
208 """Find/create a profile dir and return its ProfileDir.
209
209
210 This will create the profile directory if it doesn't exist.
210 This will create the profile directory if it doesn't exist.
211
211
212 Parameters
212 Parameters
213 ----------
213 ----------
214 profile_dir : unicode or str
214 profile_dir : unicode or str
215 The path of the profile directory. This is expanded using
215 The path of the profile directory. This is expanded using
216 :func:`IPython.utils.genutils.expand_path`.
216 :func:`IPython.utils.genutils.expand_path`.
217 """
217 """
218 profile_dir = expand_path(profile_dir)
218 profile_dir = expand_path(profile_dir)
219 if not os.path.isdir(profile_dir):
219 if not os.path.isdir(profile_dir):
220 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
220 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
221 return cls(location=profile_dir, config=config)
221 return cls(location=profile_dir, config=config)
222
222
223
223
224 #-----------------------------------------------------------------------------
224 #-----------------------------------------------------------------------------
225 # Base Application Class
225 # Base Application Class
226 #-----------------------------------------------------------------------------
226 #-----------------------------------------------------------------------------
227
227
228 # aliases and flags
228 # aliases and flags
229
229
230 base_aliases = dict(
230 base_aliases = dict(
231 profile='BaseIPythonApplication.profile',
231 profile='BaseIPythonApplication.profile',
232 ipython_dir='BaseIPythonApplication.ipython_dir',
232 ipython_dir='BaseIPythonApplication.ipython_dir',
233 )
233 )
234
234
235 base_flags = dict(
235 base_flags = dict(
236 debug = ({'Application' : Config({'log_level' : logging.DEBUG})},
236 debug = ({'Application' : {'log_level' : logging.DEBUG}},
237 "set log level to logging.DEBUG (maximize logging output)"),
237 "set log level to logging.DEBUG (maximize logging output)"),
238 quiet = ({'Application' : Config({'log_level' : logging.CRITICAL})},
238 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
239 "set log level to logging.CRITICAL (minimize logging output)"),
239 "set log level to logging.CRITICAL (minimize logging output)"),
240 init = ({'BaseIPythonApplication' : Config({
240 init = ({'BaseIPythonApplication' : {
241 'copy_config_files' : True,
241 'copy_config_files' : True,
242 'auto_create' : True})
242 'auto_create' : True}
243 }, "Initialize profile with default config files")
243 }, "Initialize profile with default config files")
244 )
244 )
245
245
246
246
247 class BaseIPythonApplication(Application):
247 class BaseIPythonApplication(Application):
248
248
249 name = Unicode(u'ipython')
249 name = Unicode(u'ipython')
250 description = Unicode(u'IPython: an enhanced interactive Python shell.')
250 description = Unicode(u'IPython: an enhanced interactive Python shell.')
251 version = Unicode(release.version)
251 version = Unicode(release.version)
252
252
253 aliases = Dict(base_aliases)
253 aliases = Dict(base_aliases)
254 flags = Dict(base_flags)
254 flags = Dict(base_flags)
255
255
256 # Track whether the config_file has changed,
256 # Track whether the config_file has changed,
257 # because some logic happens only if we aren't using the default.
257 # because some logic happens only if we aren't using the default.
258 config_file_specified = Bool(False)
258 config_file_specified = Bool(False)
259
259
260 config_file_name = Unicode(u'ipython_config.py')
260 config_file_name = Unicode(u'ipython_config.py')
261 def _config_file_name_changed(self, name, old, new):
261 def _config_file_name_changed(self, name, old, new):
262 if new != old:
262 if new != old:
263 self.config_file_specified = True
263 self.config_file_specified = True
264
264
265 # The directory that contains IPython's builtin profiles.
265 # The directory that contains IPython's builtin profiles.
266 builtin_profile_dir = Unicode(
266 builtin_profile_dir = Unicode(
267 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
267 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
268 )
268 )
269
269
270 config_file_paths = List(Unicode)
270 config_file_paths = List(Unicode)
271 def _config_file_paths_default(self):
271 def _config_file_paths_default(self):
272 return [os.getcwdu()]
272 return [os.getcwdu()]
273
273
274 profile = Unicode(u'default', config=True,
274 profile = Unicode(u'default', config=True,
275 help="""The IPython profile to use."""
275 help="""The IPython profile to use."""
276 )
276 )
277 def _profile_changed(self, name, old, new):
277 def _profile_changed(self, name, old, new):
278 self.builtin_profile_dir = os.path.join(
278 self.builtin_profile_dir = os.path.join(
279 get_ipython_package_dir(), u'config', u'profile', new
279 get_ipython_package_dir(), u'config', u'profile', new
280 )
280 )
281
281
282
282
283 ipython_dir = Unicode(get_ipython_dir(), config=True,
283 ipython_dir = Unicode(get_ipython_dir(), config=True,
284 help="""
284 help="""
285 The name of the IPython directory. This directory is used for logging
285 The name of the IPython directory. This directory is used for logging
286 configuration (through profiles), history storage, etc. The default
286 configuration (through profiles), history storage, etc. The default
287 is usually $HOME/.ipython. This options can also be specified through
287 is usually $HOME/.ipython. This options can also be specified through
288 the environment variable IPYTHON_DIR.
288 the environment variable IPYTHON_DIR.
289 """
289 """
290 )
290 )
291
291
292 overwrite = Bool(False, config=True,
292 overwrite = Bool(False, config=True,
293 help="""Whether to overwrite existing config files when copying""")
293 help="""Whether to overwrite existing config files when copying""")
294 auto_create = Bool(False, config=True,
294 auto_create = Bool(False, config=True,
295 help="""Whether to create profile dir if it doesn't exist""")
295 help="""Whether to create profile dir if it doesn't exist""")
296
296
297 config_files = List(Unicode)
297 config_files = List(Unicode)
298 def _config_files_default(self):
298 def _config_files_default(self):
299 return [u'ipython_config.py']
299 return [u'ipython_config.py']
300
300
301 copy_config_files = Bool(False, config=True,
301 copy_config_files = Bool(False, config=True,
302 help="""Whether to copy the default config files into the profile dir.""")
302 help="""Whether to copy the default config files into the profile dir.""")
303
303
304 # The class to use as the crash handler.
304 # The class to use as the crash handler.
305 crash_handler_class = Type(crashhandler.CrashHandler)
305 crash_handler_class = Type(crashhandler.CrashHandler)
306
306
307 def __init__(self, **kwargs):
307 def __init__(self, **kwargs):
308 super(BaseIPythonApplication, self).__init__(**kwargs)
308 super(BaseIPythonApplication, self).__init__(**kwargs)
309 # ensure even default IPYTHON_DIR exists
309 # ensure even default IPYTHON_DIR exists
310 if not os.path.exists(self.ipython_dir):
310 if not os.path.exists(self.ipython_dir):
311 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
311 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
312
312
313 #-------------------------------------------------------------------------
313 #-------------------------------------------------------------------------
314 # Various stages of Application creation
314 # Various stages of Application creation
315 #-------------------------------------------------------------------------
315 #-------------------------------------------------------------------------
316
316
317 def init_crash_handler(self):
317 def init_crash_handler(self):
318 """Create a crash handler, typically setting sys.excepthook to it."""
318 """Create a crash handler, typically setting sys.excepthook to it."""
319 self.crash_handler = self.crash_handler_class(self)
319 self.crash_handler = self.crash_handler_class(self)
320 sys.excepthook = self.crash_handler
320 sys.excepthook = self.crash_handler
321
321
322 def _ipython_dir_changed(self, name, old, new):
322 def _ipython_dir_changed(self, name, old, new):
323 if old in sys.path:
323 if old in sys.path:
324 sys.path.remove(old)
324 sys.path.remove(old)
325 sys.path.append(os.path.abspath(new))
325 sys.path.append(os.path.abspath(new))
326 if not os.path.isdir(new):
326 if not os.path.isdir(new):
327 os.makedirs(new, mode=0777)
327 os.makedirs(new, mode=0777)
328 readme = os.path.join(new, 'README')
328 readme = os.path.join(new, 'README')
329 if not os.path.exists(readme):
329 if not os.path.exists(readme):
330 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
330 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
331 shutil.copy(os.path.join(path, 'README'), readme)
331 shutil.copy(os.path.join(path, 'README'), readme)
332 self.log.debug("IPYTHON_DIR set to: %s" % new)
332 self.log.debug("IPYTHON_DIR set to: %s" % new)
333
333
334 def load_config_file(self, suppress_errors=True):
334 def load_config_file(self, suppress_errors=True):
335 """Load the config file.
335 """Load the config file.
336
336
337 By default, errors in loading config are handled, and a warning
337 By default, errors in loading config are handled, and a warning
338 printed on screen. For testing, the suppress_errors option is set
338 printed on screen. For testing, the suppress_errors option is set
339 to False, so errors will make tests fail.
339 to False, so errors will make tests fail.
340 """
340 """
341 self.log.debug("Attempting to load config file: %s" %
341 self.log.debug("Attempting to load config file: %s" %
342 self.config_file_name)
342 self.config_file_name)
343 try:
343 try:
344 Application.load_config_file(
344 Application.load_config_file(
345 self,
345 self,
346 self.config_file_name,
346 self.config_file_name,
347 path=self.config_file_paths
347 path=self.config_file_paths
348 )
348 )
349 except IOError:
349 except IOError:
350 # Only warn if the default config file was NOT being used.
350 # Only warn if the default config file was NOT being used.
351 if self.config_file_specified:
351 if self.config_file_specified:
352 self.log.warn("Config file not found, skipping: %s" %
352 self.log.warn("Config file not found, skipping: %s" %
353 self.config_file_name)
353 self.config_file_name)
354 except:
354 except:
355 # For testing purposes.
355 # For testing purposes.
356 if not suppress_errors:
356 if not suppress_errors:
357 raise
357 raise
358 self.log.warn("Error loading config file: %s" %
358 self.log.warn("Error loading config file: %s" %
359 self.config_file_name, exc_info=True)
359 self.config_file_name, exc_info=True)
360
360
361 def init_profile_dir(self):
361 def init_profile_dir(self):
362 """initialize the profile dir"""
362 """initialize the profile dir"""
363 try:
363 try:
364 # location explicitly specified:
364 # location explicitly specified:
365 location = self.config.ProfileDir.location
365 location = self.config.ProfileDir.location
366 except AttributeError:
366 except AttributeError:
367 # location not specified, find by profile name
367 # location not specified, find by profile name
368 try:
368 try:
369 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
369 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
370 except ProfileDirError:
370 except ProfileDirError:
371 # not found, maybe create it (always create default profile)
371 # not found, maybe create it (always create default profile)
372 if self.auto_create or self.profile=='default':
372 if self.auto_create or self.profile=='default':
373 try:
373 try:
374 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
374 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
375 except ProfileDirError:
375 except ProfileDirError:
376 self.log.fatal("Could not create profile: %r"%self.profile)
376 self.log.fatal("Could not create profile: %r"%self.profile)
377 self.exit(1)
377 self.exit(1)
378 else:
378 else:
379 self.log.info("Created profile dir: %r"%p.location)
379 self.log.info("Created profile dir: %r"%p.location)
380 else:
380 else:
381 self.log.fatal("Profile %r not found."%self.profile)
381 self.log.fatal("Profile %r not found."%self.profile)
382 self.exit(1)
382 self.exit(1)
383 else:
383 else:
384 self.log.info("Using existing profile dir: %r"%p.location)
384 self.log.info("Using existing profile dir: %r"%p.location)
385 else:
385 else:
386 # location is fully specified
386 # location is fully specified
387 try:
387 try:
388 p = ProfileDir.find_profile_dir(location, self.config)
388 p = ProfileDir.find_profile_dir(location, self.config)
389 except ProfileDirError:
389 except ProfileDirError:
390 # not found, maybe create it
390 # not found, maybe create it
391 if self.auto_create:
391 if self.auto_create:
392 try:
392 try:
393 p = ProfileDir.create_profile_dir(location, self.config)
393 p = ProfileDir.create_profile_dir(location, self.config)
394 except ProfileDirError:
394 except ProfileDirError:
395 self.log.fatal("Could not create profile directory: %r"%location)
395 self.log.fatal("Could not create profile directory: %r"%location)
396 self.exit(1)
396 self.exit(1)
397 else:
397 else:
398 self.log.info("Creating new profile dir: %r"%location)
398 self.log.info("Creating new profile dir: %r"%location)
399 else:
399 else:
400 self.log.fatal("Profile directory %r not found."%location)
400 self.log.fatal("Profile directory %r not found."%location)
401 self.exit(1)
401 self.exit(1)
402 else:
402 else:
403 self.log.info("Using existing profile dir: %r"%location)
403 self.log.info("Using existing profile dir: %r"%location)
404
404
405 self.profile_dir = p
405 self.profile_dir = p
406 self.config_file_paths.append(p.location)
406 self.config_file_paths.append(p.location)
407
407
408 def init_config_files(self):
408 def init_config_files(self):
409 """[optionally] copy default config files into profile dir."""
409 """[optionally] copy default config files into profile dir."""
410 # copy config files
410 # copy config files
411 if self.copy_config_files:
411 if self.copy_config_files:
412 path = self.builtin_profile_dir
412 path = self.builtin_profile_dir
413 src = self.profile
413 src = self.profile
414 if not os.path.exists(path):
414 if not os.path.exists(path):
415 # use default if new profile doesn't have a preset
415 # use default if new profile doesn't have a preset
416 path = None
416 path = None
417 src = 'default'
417 src = 'default'
418
418
419 self.log.debug("Staging %s config files into %r [overwrite=%s]"%(
419 self.log.debug("Staging %s config files into %r [overwrite=%s]"%(
420 src, self.profile_dir.location, self.overwrite)
420 src, self.profile_dir.location, self.overwrite)
421 )
421 )
422
422
423 for cfg in self.config_files:
423 for cfg in self.config_files:
424 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
424 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
425
425
426 def initialize(self, argv=None):
426 def initialize(self, argv=None):
427 self.init_crash_handler()
427 self.init_crash_handler()
428 self.parse_command_line(argv)
428 self.parse_command_line(argv)
429 cl_config = self.config
429 cl_config = self.config
430 self.init_profile_dir()
430 self.init_profile_dir()
431 self.init_config_files()
431 self.init_config_files()
432 self.load_config_file()
432 self.load_config_file()
433 # enforce cl-opts override configfile opts:
433 # enforce cl-opts override configfile opts:
434 self.update_config(cl_config)
434 self.update_config(cl_config)
435
435
General Comments 0
You need to be logged in to leave comments. Login now