##// END OF EJS Templates
don't crash apps on TraitErrors when parsing command-line
MinRK -
Show More
@@ -1,393 +1,393 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 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 from copy import deepcopy
26 from copy import deepcopy
27
27
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
29 from IPython.config.loader import (
29 from IPython.config.loader import (
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
31 )
31 )
32
32
33 from IPython.utils.traitlets import (
33 from IPython.utils.traitlets import (
34 Unicode, List, Int, Enum, Dict, Instance
34 Unicode, List, Int, Enum, Dict, Instance, TraitError
35 )
35 )
36 from IPython.utils.importstring import import_item
36 from IPython.utils.importstring import import_item
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # function for re-wrapping a helpstring
40 # function for re-wrapping a helpstring
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Descriptions for the various sections
44 # Descriptions for the various sections
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 flag_description = """
47 flag_description = """
48 Flags are command-line arguments passed as '--<flag>'.
48 Flags are command-line arguments passed as '--<flag>'.
49 These take no parameters, unlike regular key-value arguments.
49 These take no parameters, unlike regular key-value arguments.
50 They are typically used for setting boolean flags, or enabling
50 They are typically used for setting boolean flags, or enabling
51 modes that involve setting multiple options together.
51 modes that involve setting multiple options together.
52
52
53 Flags *always* begin with '--', never just one '-'.
53 Flags *always* begin with '--', never just one '-'.
54 """.strip() # trim newlines of front and back
54 """.strip() # trim newlines of front and back
55
55
56 alias_description = """
56 alias_description = """
57 These are commonly set parameters, given abbreviated aliases for convenience.
57 These are commonly set parameters, given abbreviated aliases for convenience.
58 They are set in the same `name=value` way as class parameters, where
58 They are set in the same `name=value` way as class parameters, where
59 <name> is replaced by the real parameter for which it is an alias.
59 <name> is replaced by the real parameter for which it is an alias.
60 """.strip() # trim newlines of front and back
60 """.strip() # trim newlines of front and back
61
61
62 keyvalue_description = """
62 keyvalue_description = """
63 Parameters are set from command-line arguments of the form:
63 Parameters are set from command-line arguments of the form:
64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
65 This line is evaluated in Python, so simple expressions are allowed, e.g.
65 This line is evaluated in Python, so simple expressions are allowed, e.g.
66 `C.a='range(3)'` For setting C.a=[0,1,2]
66 `C.a='range(3)'` For setting C.a=[0,1,2]
67 """.strip() # trim newlines of front and back
67 """.strip() # trim newlines of front and back
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Application class
70 # Application class
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73
73
74 class ApplicationError(Exception):
74 class ApplicationError(Exception):
75 pass
75 pass
76
76
77
77
78 class Application(SingletonConfigurable):
78 class Application(SingletonConfigurable):
79 """A singleton application with full configuration support."""
79 """A singleton application with full configuration support."""
80
80
81 # The name of the application, will usually match the name of the command
81 # The name of the application, will usually match the name of the command
82 # line application
82 # line application
83 name = Unicode(u'application')
83 name = Unicode(u'application')
84
84
85 # The description of the application that is printed at the beginning
85 # The description of the application that is printed at the beginning
86 # of the help.
86 # of the help.
87 description = Unicode(u'This is an application.')
87 description = Unicode(u'This is an application.')
88 # default section descriptions
88 # default section descriptions
89 flag_description = Unicode(flag_description)
89 flag_description = Unicode(flag_description)
90 alias_description = Unicode(alias_description)
90 alias_description = Unicode(alias_description)
91 keyvalue_description = Unicode(keyvalue_description)
91 keyvalue_description = Unicode(keyvalue_description)
92
92
93
93
94 # A sequence of Configurable subclasses whose config=True attributes will
94 # A sequence of Configurable subclasses whose config=True attributes will
95 # be exposed at the command line.
95 # be exposed at the command line.
96 classes = List([])
96 classes = List([])
97
97
98 # The version string of this application.
98 # The version string of this application.
99 version = Unicode(u'0.0')
99 version = Unicode(u'0.0')
100
100
101 # The log level for the application
101 # The log level for the application
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
103 default_value=logging.WARN,
104 config=True,
104 config=True,
105 help="Set the log level by value or name.")
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
106 def _log_level_changed(self, name, old, new):
107 """Adjust the log level when log_level is set."""
107 """Adjust the log level when log_level is set."""
108 if isinstance(new, basestring):
108 if isinstance(new, basestring):
109 new = getattr(logging, new)
109 new = getattr(logging, new)
110 self.log_level = new
110 self.log_level = new
111 self.log.setLevel(new)
111 self.log.setLevel(new)
112
112
113 # the alias map for configurables
113 # the alias map for configurables
114 aliases = Dict(dict(log_level='Application.log_level'))
114 aliases = Dict(dict(log_level='Application.log_level'))
115
115
116 # flags for loading Configurables or store_const style flags
116 # flags for loading Configurables or store_const style flags
117 # flags are loaded from this dict by '--key' flags
117 # flags are loaded from this dict by '--key' flags
118 # this must be a dict of two-tuples, the first element being the Config/dict
118 # this must be a dict of two-tuples, the first element being the Config/dict
119 # and the second being the help string for the flag
119 # and the second being the help string for the flag
120 flags = Dict()
120 flags = Dict()
121 def _flags_changed(self, name, old, new):
121 def _flags_changed(self, name, old, new):
122 """ensure flags dict is valid"""
122 """ensure flags dict is valid"""
123 for key,value in new.iteritems():
123 for key,value in new.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
127
128
128
129 # subcommands for launching other applications
129 # subcommands for launching other applications
130 # if this is not empty, this will be a parent Application
130 # if this is not empty, this will be a parent Application
131 # this must be a dict of two-tuples,
131 # this must be a dict of two-tuples,
132 # the first element being the application class/import string
132 # the first element being the application class/import string
133 # and the second being the help string for the subcommand
133 # and the second being the help string for the subcommand
134 subcommands = Dict()
134 subcommands = Dict()
135 # parse_command_line will initialize a subapp, if requested
135 # parse_command_line will initialize a subapp, if requested
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
137
137
138 # extra command-line arguments that don't set config values
138 # extra command-line arguments that don't set config values
139 extra_args = List(Unicode)
139 extra_args = List(Unicode)
140
140
141
141
142 def __init__(self, **kwargs):
142 def __init__(self, **kwargs):
143 SingletonConfigurable.__init__(self, **kwargs)
143 SingletonConfigurable.__init__(self, **kwargs)
144 # Add my class to self.classes so my attributes appear in command line
144 # Add my class to self.classes so my attributes appear in command line
145 # options.
145 # options.
146 self.classes.insert(0, self.__class__)
146 self.classes.insert(0, self.__class__)
147
147
148 self.init_logging()
148 self.init_logging()
149
149
150 def _config_changed(self, name, old, new):
150 def _config_changed(self, name, old, new):
151 SingletonConfigurable._config_changed(self, name, old, new)
151 SingletonConfigurable._config_changed(self, name, old, new)
152 self.log.debug('Config changed:')
152 self.log.debug('Config changed:')
153 self.log.debug(repr(new))
153 self.log.debug(repr(new))
154
154
155 def init_logging(self):
155 def init_logging(self):
156 """Start logging for this application.
156 """Start logging for this application.
157
157
158 The default is to log to stdout using a StreaHandler. The log level
158 The default is to log to stdout using a StreaHandler. The log level
159 starts at loggin.WARN, but this can be adjusted by setting the
159 starts at loggin.WARN, but this can be adjusted by setting the
160 ``log_level`` attribute.
160 ``log_level`` attribute.
161 """
161 """
162 self.log = logging.getLogger(self.__class__.__name__)
162 self.log = logging.getLogger(self.__class__.__name__)
163 self.log.setLevel(self.log_level)
163 self.log.setLevel(self.log_level)
164 self._log_handler = logging.StreamHandler()
164 self._log_handler = logging.StreamHandler()
165 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
165 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
166 self._log_handler.setFormatter(self._log_formatter)
166 self._log_handler.setFormatter(self._log_formatter)
167 self.log.addHandler(self._log_handler)
167 self.log.addHandler(self._log_handler)
168
168
169 def initialize(self, argv=None):
169 def initialize(self, argv=None):
170 """Do the basic steps to configure me.
170 """Do the basic steps to configure me.
171
171
172 Override in subclasses.
172 Override in subclasses.
173 """
173 """
174 self.parse_command_line(argv)
174 self.parse_command_line(argv)
175
175
176
176
177 def start(self):
177 def start(self):
178 """Start the app mainloop.
178 """Start the app mainloop.
179
179
180 Override in subclasses.
180 Override in subclasses.
181 """
181 """
182 if self.subapp is not None:
182 if self.subapp is not None:
183 return self.subapp.start()
183 return self.subapp.start()
184
184
185 def print_alias_help(self):
185 def print_alias_help(self):
186 """Print the alias part of the help."""
186 """Print the alias part of the help."""
187 if not self.aliases:
187 if not self.aliases:
188 return
188 return
189
189
190 lines = ['Aliases']
190 lines = ['Aliases']
191 lines.append('-'*len(lines[0]))
191 lines.append('-'*len(lines[0]))
192 lines.append('')
192 lines.append('')
193 for p in wrap_paragraphs(self.alias_description):
193 for p in wrap_paragraphs(self.alias_description):
194 lines.append(p)
194 lines.append(p)
195 lines.append('')
195 lines.append('')
196
196
197 classdict = {}
197 classdict = {}
198 for cls in self.classes:
198 for cls in self.classes:
199 # include all parents (up to, but excluding Configurable) in available names
199 # include all parents (up to, but excluding Configurable) in available names
200 for c in cls.mro()[:-3]:
200 for c in cls.mro()[:-3]:
201 classdict[c.__name__] = c
201 classdict[c.__name__] = c
202
202
203 for alias, longname in self.aliases.iteritems():
203 for alias, longname in self.aliases.iteritems():
204 classname, traitname = longname.split('.',1)
204 classname, traitname = longname.split('.',1)
205 cls = classdict[classname]
205 cls = classdict[classname]
206
206
207 trait = cls.class_traits(config=True)[traitname]
207 trait = cls.class_traits(config=True)[traitname]
208 help = cls.class_get_trait_help(trait)
208 help = cls.class_get_trait_help(trait)
209 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
209 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
210 lines.append(help)
210 lines.append(help)
211 lines.append('')
211 lines.append('')
212 print '\n'.join(lines)
212 print '\n'.join(lines)
213
213
214 def print_flag_help(self):
214 def print_flag_help(self):
215 """Print the flag part of the help."""
215 """Print the flag part of the help."""
216 if not self.flags:
216 if not self.flags:
217 return
217 return
218
218
219 lines = ['Flags']
219 lines = ['Flags']
220 lines.append('-'*len(lines[0]))
220 lines.append('-'*len(lines[0]))
221 lines.append('')
221 lines.append('')
222 for p in wrap_paragraphs(self.flag_description):
222 for p in wrap_paragraphs(self.flag_description):
223 lines.append(p)
223 lines.append(p)
224 lines.append('')
224 lines.append('')
225
225
226 for m, (cfg,help) in self.flags.iteritems():
226 for m, (cfg,help) in self.flags.iteritems():
227 lines.append('--'+m)
227 lines.append('--'+m)
228 lines.append(indent(dedent(help.strip())))
228 lines.append(indent(dedent(help.strip())))
229 lines.append('')
229 lines.append('')
230 print '\n'.join(lines)
230 print '\n'.join(lines)
231
231
232 def print_subcommands(self):
232 def print_subcommands(self):
233 """Print the subcommand part of the help."""
233 """Print the subcommand part of the help."""
234 if not self.subcommands:
234 if not self.subcommands:
235 return
235 return
236
236
237 lines = ["Subcommands"]
237 lines = ["Subcommands"]
238 lines.append('-'*len(lines[0]))
238 lines.append('-'*len(lines[0]))
239 for subc, (cls,help) in self.subcommands.iteritems():
239 for subc, (cls,help) in self.subcommands.iteritems():
240 lines.append("%s : %s"%(subc, cls))
240 lines.append("%s : %s"%(subc, cls))
241 if help:
241 if help:
242 lines.append(indent(dedent(help.strip())))
242 lines.append(indent(dedent(help.strip())))
243 lines.append('')
243 lines.append('')
244 print '\n'.join(lines)
244 print '\n'.join(lines)
245
245
246 def print_help(self, classes=False):
246 def print_help(self, classes=False):
247 """Print the help for each Configurable class in self.classes.
247 """Print the help for each Configurable class in self.classes.
248
248
249 If classes=False (the default), only flags and aliases are printed.
249 If classes=False (the default), only flags and aliases are printed.
250 """
250 """
251 self.print_subcommands()
251 self.print_subcommands()
252 self.print_flag_help()
252 self.print_flag_help()
253 self.print_alias_help()
253 self.print_alias_help()
254
254
255 if classes:
255 if classes:
256 if self.classes:
256 if self.classes:
257 print "Class parameters"
257 print "Class parameters"
258 print "----------------"
258 print "----------------"
259 print
259 print
260 for p in wrap_paragraphs(self.keyvalue_description):
260 for p in wrap_paragraphs(self.keyvalue_description):
261 print p
261 print p
262 print
262 print
263
263
264 for cls in self.classes:
264 for cls in self.classes:
265 cls.class_print_help()
265 cls.class_print_help()
266 print
266 print
267 else:
267 else:
268 print "To see all available configurables, use `--help-all`"
268 print "To see all available configurables, use `--help-all`"
269 print
269 print
270
270
271 def print_description(self):
271 def print_description(self):
272 """Print the application description."""
272 """Print the application description."""
273 for p in wrap_paragraphs(self.description):
273 for p in wrap_paragraphs(self.description):
274 print p
274 print p
275 print
275 print
276
276
277 def print_version(self):
277 def print_version(self):
278 """Print the version string."""
278 """Print the version string."""
279 print self.version
279 print self.version
280
280
281 def update_config(self, config):
281 def update_config(self, config):
282 """Fire the traits events when the config is updated."""
282 """Fire the traits events when the config is updated."""
283 # Save a copy of the current config.
283 # Save a copy of the current config.
284 newconfig = deepcopy(self.config)
284 newconfig = deepcopy(self.config)
285 # Merge the new config into the current one.
285 # Merge the new config into the current one.
286 newconfig._merge(config)
286 newconfig._merge(config)
287 # Save the combined config as self.config, which triggers the traits
287 # Save the combined config as self.config, which triggers the traits
288 # events.
288 # events.
289 self.config = newconfig
289 self.config = newconfig
290
290
291 def initialize_subcommand(self, subc, argv=None):
291 def initialize_subcommand(self, subc, argv=None):
292 """Initialize a subcommand with argv."""
292 """Initialize a subcommand with argv."""
293 subapp,help = self.subcommands.get(subc)
293 subapp,help = self.subcommands.get(subc)
294
294
295 if isinstance(subapp, basestring):
295 if isinstance(subapp, basestring):
296 subapp = import_item(subapp)
296 subapp = import_item(subapp)
297
297
298 # clear existing instances
298 # clear existing instances
299 self.__class__.clear_instance()
299 self.__class__.clear_instance()
300 # instantiate
300 # instantiate
301 self.subapp = subapp.instance()
301 self.subapp = subapp.instance()
302 # and initialize subapp
302 # and initialize subapp
303 self.subapp.initialize(argv)
303 self.subapp.initialize(argv)
304
304
305 def parse_command_line(self, argv=None):
305 def parse_command_line(self, argv=None):
306 """Parse the command line arguments."""
306 """Parse the command line arguments."""
307 argv = sys.argv[1:] if argv is None else argv
307 argv = sys.argv[1:] if argv is None else argv
308
308
309 if self.subcommands and len(argv) > 0:
309 if self.subcommands and len(argv) > 0:
310 # we have subcommands, and one may have been specified
310 # we have subcommands, and one may have been specified
311 subc, subargv = argv[0], argv[1:]
311 subc, subargv = argv[0], argv[1:]
312 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
312 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
313 # it's a subcommand, and *not* a flag or class parameter
313 # it's a subcommand, and *not* a flag or class parameter
314 return self.initialize_subcommand(subc, subargv)
314 return self.initialize_subcommand(subc, subargv)
315
315
316 if '-h' in argv or '--help' in argv or '--help-all' in argv:
316 if '-h' in argv or '--help' in argv or '--help-all' in argv:
317 self.print_description()
317 self.print_description()
318 self.print_help('--help-all' in argv)
318 self.print_help('--help-all' in argv)
319 self.exit(0)
319 self.exit(0)
320
320
321 if '--version' in argv:
321 if '--version' in argv:
322 self.print_version()
322 self.print_version()
323 self.exit(0)
323 self.exit(0)
324
324
325 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
325 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
326 flags=self.flags)
326 flags=self.flags)
327 try:
327 try:
328 config = loader.load_config()
328 config = loader.load_config()
329 except ArgumentError as e:
329 self.update_config(config)
330 self.log.fatal(str(e))
330 except (TraitError, ArgumentError) as e:
331 self.print_description()
331 self.print_description()
332 self.print_help()
332 self.print_help()
333 self.log.fatal(str(e))
333 self.exit(1)
334 self.exit(1)
334 self.update_config(config)
335 # store unparsed args in extra_args
335 # store unparsed args in extra_args
336 self.extra_args = loader.extra_args
336 self.extra_args = loader.extra_args
337
337
338 def load_config_file(self, filename, path=None):
338 def load_config_file(self, filename, path=None):
339 """Load a .py based config file by filename and path."""
339 """Load a .py based config file by filename and path."""
340 loader = PyFileConfigLoader(filename, path=path)
340 loader = PyFileConfigLoader(filename, path=path)
341 config = loader.load_config()
341 config = loader.load_config()
342 self.update_config(config)
342 self.update_config(config)
343
343
344 def generate_config_file(self):
344 def generate_config_file(self):
345 """generate default config file from Configurables"""
345 """generate default config file from Configurables"""
346 lines = ["# Configuration file for %s."%self.name]
346 lines = ["# Configuration file for %s."%self.name]
347 lines.append('')
347 lines.append('')
348 lines.append('c = get_config()')
348 lines.append('c = get_config()')
349 lines.append('')
349 lines.append('')
350 for cls in self.classes:
350 for cls in self.classes:
351 lines.append(cls.class_config_section())
351 lines.append(cls.class_config_section())
352 return '\n'.join(lines)
352 return '\n'.join(lines)
353
353
354 def exit(self, exit_status=0):
354 def exit(self, exit_status=0):
355 self.log.debug("Exiting application: %s" % self.name)
355 self.log.debug("Exiting application: %s" % self.name)
356 sys.exit(exit_status)
356 sys.exit(exit_status)
357
357
358 #-----------------------------------------------------------------------------
358 #-----------------------------------------------------------------------------
359 # utility functions, for convenience
359 # utility functions, for convenience
360 #-----------------------------------------------------------------------------
360 #-----------------------------------------------------------------------------
361
361
362 def boolean_flag(name, configurable, set_help='', unset_help=''):
362 def boolean_flag(name, configurable, set_help='', unset_help=''):
363 """Helper for building basic --trait, --no-trait flags.
363 """Helper for building basic --trait, --no-trait flags.
364
364
365 Parameters
365 Parameters
366 ----------
366 ----------
367
367
368 name : str
368 name : str
369 The name of the flag.
369 The name of the flag.
370 configurable : str
370 configurable : str
371 The 'Class.trait' string of the trait to be set/unset with the flag
371 The 'Class.trait' string of the trait to be set/unset with the flag
372 set_help : unicode
372 set_help : unicode
373 help string for --name flag
373 help string for --name flag
374 unset_help : unicode
374 unset_help : unicode
375 help string for --no-name flag
375 help string for --no-name flag
376
376
377 Returns
377 Returns
378 -------
378 -------
379
379
380 cfg : dict
380 cfg : dict
381 A dict with two keys: 'name', and 'no-name', for setting and unsetting
381 A dict with two keys: 'name', and 'no-name', for setting and unsetting
382 the trait, respectively.
382 the trait, respectively.
383 """
383 """
384 # default helpstrings
384 # default helpstrings
385 set_help = set_help or "set %s=True"%configurable
385 set_help = set_help or "set %s=True"%configurable
386 unset_help = unset_help or "set %s=False"%configurable
386 unset_help = unset_help or "set %s=False"%configurable
387
387
388 cls,trait = configurable.split('.')
388 cls,trait = configurable.split('.')
389
389
390 setter = {cls : {trait : True}}
390 setter = {cls : {trait : True}}
391 unsetter = {cls : {trait : False}}
391 unsetter = {cls : {trait : False}}
392 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
392 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
393
393
@@ -1,291 +1,292 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 * Min RK
15 * Min RK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 import logging
30 import logging
31 import os
31 import os
32 import shutil
32 import shutil
33 import sys
33 import sys
34
34
35 from IPython.config.application import Application
35 from IPython.config.application import Application
36 from IPython.config.configurable import Configurable
36 from IPython.config.configurable import Configurable
37 from IPython.config.loader import Config
37 from IPython.config.loader import Config
38 from IPython.core import release, crashhandler
38 from IPython.core import release, crashhandler
39 from IPython.core.profiledir import ProfileDir, ProfileDirError
39 from IPython.core.profiledir import ProfileDir, ProfileDirError
40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
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 # Base Application Class
49 # Base Application Class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 # aliases and flags
52 # aliases and flags
53
53
54 base_aliases = dict(
54 base_aliases = dict(
55 profile='BaseIPythonApplication.profile',
55 profile='BaseIPythonApplication.profile',
56 ipython_dir='BaseIPythonApplication.ipython_dir',
56 ipython_dir='BaseIPythonApplication.ipython_dir',
57 log_level='Application.log_level',
57 log_level='Application.log_level',
58 )
58 )
59
59
60 base_flags = dict(
60 base_flags = dict(
61 debug = ({'Application' : {'log_level' : logging.DEBUG}},
61 debug = ({'Application' : {'log_level' : logging.DEBUG}},
62 "set log level to logging.DEBUG (maximize logging output)"),
62 "set log level to logging.DEBUG (maximize logging output)"),
63 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
63 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
64 "set log level to logging.CRITICAL (minimize logging output)"),
64 "set log level to logging.CRITICAL (minimize logging output)"),
65 init = ({'BaseIPythonApplication' : {
65 init = ({'BaseIPythonApplication' : {
66 'copy_config_files' : True,
66 'copy_config_files' : True,
67 'auto_create' : True}
67 'auto_create' : True}
68 }, "Initialize profile with default config files")
68 }, "Initialize profile with default config files")
69 )
69 )
70
70
71
71
72 class BaseIPythonApplication(Application):
72 class BaseIPythonApplication(Application):
73
73
74 name = Unicode(u'ipython')
74 name = Unicode(u'ipython')
75 description = Unicode(u'IPython: an enhanced interactive Python shell.')
75 description = Unicode(u'IPython: an enhanced interactive Python shell.')
76 version = Unicode(release.version)
76 version = Unicode(release.version)
77
77
78 aliases = Dict(base_aliases)
78 aliases = Dict(base_aliases)
79 flags = Dict(base_flags)
79 flags = Dict(base_flags)
80
80
81 # Track whether the config_file has changed,
81 # Track whether the config_file has changed,
82 # because some logic happens only if we aren't using the default.
82 # because some logic happens only if we aren't using the default.
83 config_file_specified = Bool(False)
83 config_file_specified = Bool(False)
84
84
85 config_file_name = Unicode(u'ipython_config.py')
85 config_file_name = Unicode(u'ipython_config.py')
86 def _config_file_name_default(self):
86 def _config_file_name_default(self):
87 return self.name.replace('-','_') + u'_config.py'
87 return self.name.replace('-','_') + u'_config.py'
88 def _config_file_name_changed(self, name, old, new):
88 def _config_file_name_changed(self, name, old, new):
89 if new != old:
89 if new != old:
90 self.config_file_specified = True
90 self.config_file_specified = True
91
91
92 # The directory that contains IPython's builtin profiles.
92 # The directory that contains IPython's builtin profiles.
93 builtin_profile_dir = Unicode(
93 builtin_profile_dir = Unicode(
94 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
94 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
95 )
95 )
96
96
97 config_file_paths = List(Unicode)
97 config_file_paths = List(Unicode)
98 def _config_file_paths_default(self):
98 def _config_file_paths_default(self):
99 return [os.getcwdu()]
99 return [os.getcwdu()]
100
100
101 profile = Unicode(u'default', config=True,
101 profile = Unicode(u'default', config=True,
102 help="""The IPython profile to use."""
102 help="""The IPython profile to use."""
103 )
103 )
104 def _profile_changed(self, name, old, new):
104 def _profile_changed(self, name, old, new):
105 self.builtin_profile_dir = os.path.join(
105 self.builtin_profile_dir = os.path.join(
106 get_ipython_package_dir(), u'config', u'profile', new
106 get_ipython_package_dir(), u'config', u'profile', new
107 )
107 )
108
108
109 ipython_dir = Unicode(get_ipython_dir(), config=True,
109 ipython_dir = Unicode(get_ipython_dir(), config=True,
110 help="""
110 help="""
111 The name of the IPython directory. This directory is used for logging
111 The name of the IPython directory. This directory is used for logging
112 configuration (through profiles), history storage, etc. The default
112 configuration (through profiles), history storage, etc. The default
113 is usually $HOME/.ipython. This options can also be specified through
113 is usually $HOME/.ipython. This options can also be specified through
114 the environment variable IPYTHON_DIR.
114 the environment variable IPYTHON_DIR.
115 """
115 """
116 )
116 )
117
117
118 overwrite = Bool(False, config=True,
118 overwrite = Bool(False, config=True,
119 help="""Whether to overwrite existing config files when copying""")
119 help="""Whether to overwrite existing config files when copying""")
120 auto_create = Bool(False, config=True,
120 auto_create = Bool(False, config=True,
121 help="""Whether to create profile dir if it doesn't exist""")
121 help="""Whether to create profile dir if it doesn't exist""")
122
122
123 config_files = List(Unicode)
123 config_files = List(Unicode)
124 def _config_files_default(self):
124 def _config_files_default(self):
125 return [u'ipython_config.py']
125 return [u'ipython_config.py']
126
126
127 copy_config_files = Bool(False, config=True,
127 copy_config_files = Bool(False, config=True,
128 help="""Whether to install the default config files into the profile dir.
128 help="""Whether to install the default config files into the profile dir.
129 If a new profile is being created, and IPython contains config files for that
129 If a new profile is being created, and IPython contains config files for that
130 profile, then they will be staged into the new directory. Otherwise,
130 profile, then they will be staged into the new directory. Otherwise,
131 default config files will be automatically generated.
131 default config files will be automatically generated.
132 """)
132 """)
133
133
134 # The class to use as the crash handler.
134 # The class to use as the crash handler.
135 crash_handler_class = Type(crashhandler.CrashHandler)
135 crash_handler_class = Type(crashhandler.CrashHandler)
136
136
137 def __init__(self, **kwargs):
137 def __init__(self, **kwargs):
138 super(BaseIPythonApplication, self).__init__(**kwargs)
138 super(BaseIPythonApplication, self).__init__(**kwargs)
139 # ensure even default IPYTHON_DIR exists
139 # ensure even default IPYTHON_DIR exists
140 if not os.path.exists(self.ipython_dir):
140 if not os.path.exists(self.ipython_dir):
141 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
141 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
142
142
143 #-------------------------------------------------------------------------
143 #-------------------------------------------------------------------------
144 # Various stages of Application creation
144 # Various stages of Application creation
145 #-------------------------------------------------------------------------
145 #-------------------------------------------------------------------------
146
146
147 def init_crash_handler(self):
147 def init_crash_handler(self):
148 """Create a crash handler, typically setting sys.excepthook to it."""
148 """Create a crash handler, typically setting sys.excepthook to it."""
149 self.crash_handler = self.crash_handler_class(self)
149 self.crash_handler = self.crash_handler_class(self)
150 sys.excepthook = self.crash_handler
150 sys.excepthook = self.crash_handler
151
151
152 def _ipython_dir_changed(self, name, old, new):
152 def _ipython_dir_changed(self, name, old, new):
153 if old in sys.path:
153 if old in sys.path:
154 sys.path.remove(old)
154 sys.path.remove(old)
155 sys.path.append(os.path.abspath(new))
155 sys.path.append(os.path.abspath(new))
156 if not os.path.isdir(new):
156 if not os.path.isdir(new):
157 os.makedirs(new, mode=0777)
157 os.makedirs(new, mode=0777)
158 readme = os.path.join(new, 'README')
158 readme = os.path.join(new, 'README')
159 if not os.path.exists(readme):
159 if not os.path.exists(readme):
160 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
160 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
161 shutil.copy(os.path.join(path, 'README'), readme)
161 shutil.copy(os.path.join(path, 'README'), readme)
162 self.log.debug("IPYTHON_DIR set to: %s" % new)
162 self.log.debug("IPYTHON_DIR set to: %s" % new)
163
163
164 def load_config_file(self, suppress_errors=True):
164 def load_config_file(self, suppress_errors=True):
165 """Load the config file.
165 """Load the config file.
166
166
167 By default, errors in loading config are handled, and a warning
167 By default, errors in loading config are handled, and a warning
168 printed on screen. For testing, the suppress_errors option is set
168 printed on screen. For testing, the suppress_errors option is set
169 to False, so errors will make tests fail.
169 to False, so errors will make tests fail.
170 """
170 """
171 base_config = 'ipython_config.py'
171 base_config = 'ipython_config.py'
172 self.log.debug("Attempting to load config file: %s" %
172 self.log.debug("Attempting to load config file: %s" %
173 base_config)
173 base_config)
174 try:
174 try:
175 Application.load_config_file(
175 Application.load_config_file(
176 self,
176 self,
177 base_config,
177 base_config,
178 path=self.config_file_paths
178 path=self.config_file_paths
179 )
179 )
180 except IOError:
180 except IOError:
181 # ignore errors loading parent
181 # ignore errors loading parent
182 pass
182 pass
183 if self.config_file_name == base_config:
183 if self.config_file_name == base_config:
184 # don't load secondary config
184 # don't load secondary config
185 return
185 return
186 self.log.debug("Attempting to load config file: %s" %
186 self.log.debug("Attempting to load config file: %s" %
187 self.config_file_name)
187 self.config_file_name)
188 try:
188 try:
189 Application.load_config_file(
189 Application.load_config_file(
190 self,
190 self,
191 self.config_file_name,
191 self.config_file_name,
192 path=self.config_file_paths
192 path=self.config_file_paths
193 )
193 )
194 except IOError:
194 except IOError:
195 # Only warn if the default config file was NOT being used.
195 # Only warn if the default config file was NOT being used.
196 if self.config_file_specified:
196 if self.config_file_specified:
197 self.log.warn("Config file not found, skipping: %s" %
197 self.log.warn("Config file not found, skipping: %s" %
198 self.config_file_name)
198 self.config_file_name)
199 except:
199 except:
200 # For testing purposes.
200 # For testing purposes.
201 if not suppress_errors:
201 if not suppress_errors:
202 raise
202 raise
203 self.log.warn("Error loading config file: %s" %
203 self.log.warn("Error loading config file: %s" %
204 self.config_file_name, exc_info=True)
204 self.config_file_name, exc_info=True)
205
205
206 def init_profile_dir(self):
206 def init_profile_dir(self):
207 """initialize the profile dir"""
207 """initialize the profile dir"""
208 try:
208 try:
209 # location explicitly specified:
209 # location explicitly specified:
210 location = self.config.ProfileDir.location
210 location = self.config.ProfileDir.location
211 except AttributeError:
211 except AttributeError:
212 # location not specified, find by profile name
212 # location not specified, find by profile name
213 try:
213 try:
214 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
214 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
215 except ProfileDirError:
215 except ProfileDirError:
216 # not found, maybe create it (always create default profile)
216 # not found, maybe create it (always create default profile)
217 if self.auto_create or self.profile=='default':
217 if self.auto_create or self.profile=='default':
218 try:
218 try:
219 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
219 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
220 except ProfileDirError:
220 except ProfileDirError:
221 self.log.fatal("Could not create profile: %r"%self.profile)
221 self.log.fatal("Could not create profile: %r"%self.profile)
222 self.exit(1)
222 self.exit(1)
223 else:
223 else:
224 self.log.info("Created profile dir: %r"%p.location)
224 self.log.info("Created profile dir: %r"%p.location)
225 else:
225 else:
226 self.log.fatal("Profile %r not found."%self.profile)
226 self.log.fatal("Profile %r not found."%self.profile)
227 self.exit(1)
227 self.exit(1)
228 else:
228 else:
229 self.log.info("Using existing profile dir: %r"%p.location)
229 self.log.info("Using existing profile dir: %r"%p.location)
230 else:
230 else:
231 # location is fully specified
231 # location is fully specified
232 try:
232 try:
233 p = ProfileDir.find_profile_dir(location, self.config)
233 p = ProfileDir.find_profile_dir(location, self.config)
234 except ProfileDirError:
234 except ProfileDirError:
235 # not found, maybe create it
235 # not found, maybe create it
236 if self.auto_create:
236 if self.auto_create:
237 try:
237 try:
238 p = ProfileDir.create_profile_dir(location, self.config)
238 p = ProfileDir.create_profile_dir(location, self.config)
239 except ProfileDirError:
239 except ProfileDirError:
240 self.log.fatal("Could not create profile directory: %r"%location)
240 self.log.fatal("Could not create profile directory: %r"%location)
241 self.exit(1)
241 self.exit(1)
242 else:
242 else:
243 self.log.info("Creating new profile dir: %r"%location)
243 self.log.info("Creating new profile dir: %r"%location)
244 else:
244 else:
245 self.log.fatal("Profile directory %r not found."%location)
245 self.log.fatal("Profile directory %r not found."%location)
246 self.exit(1)
246 self.exit(1)
247 else:
247 else:
248 self.log.info("Using existing profile dir: %r"%location)
248 self.log.info("Using existing profile dir: %r"%location)
249
249
250 self.profile_dir = p
250 self.profile_dir = p
251 self.config_file_paths.append(p.location)
251 self.config_file_paths.append(p.location)
252
252
253 def init_config_files(self):
253 def init_config_files(self):
254 """[optionally] copy default config files into profile dir."""
254 """[optionally] copy default config files into profile dir."""
255 # copy config files
255 # copy config files
256 if self.copy_config_files:
256 if self.copy_config_files:
257 path = self.builtin_profile_dir
257 path = self.builtin_profile_dir
258 src = self.profile
258 src = self.profile
259
259
260 cfg = self.config_file_name
260 cfg = self.config_file_name
261 if path and os.path.exists(os.path.join(path, cfg)):
261 if path and os.path.exists(os.path.join(path, cfg)):
262 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
262 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
263 cfg, src, self.profile_dir.location, self.overwrite)
263 cfg, src, self.profile_dir.location, self.overwrite)
264 )
264 )
265 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
265 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
266 else:
266 else:
267 self.stage_default_config_file()
267 self.stage_default_config_file()
268
268
269 def stage_default_config_file(self):
269 def stage_default_config_file(self):
270 """auto generate default config file, and stage it into the profile."""
270 """auto generate default config file, and stage it into the profile."""
271 s = self.generate_config_file()
271 s = self.generate_config_file()
272 fname = os.path.join(self.profile_dir.location, self.config_file_name)
272 fname = os.path.join(self.profile_dir.location, self.config_file_name)
273 if self.overwrite or not os.path.exists(fname):
273 if self.overwrite or not os.path.exists(fname):
274 self.log.warn("Generating default config file: %r"%(fname))
274 self.log.warn("Generating default config file: %r"%(fname))
275 with open(fname, 'w') as f:
275 with open(fname, 'w') as f:
276 f.write(s)
276 f.write(s)
277
277
278
278
279 def initialize(self, argv=None):
279 def initialize(self, argv=None):
280 self.init_crash_handler()
280 # don't hook up crash handler before parsing command-line
281 self.parse_command_line(argv)
281 self.parse_command_line(argv)
282 self.init_crash_handler()
282 if self.subapp is not None:
283 if self.subapp is not None:
283 # stop here if subapp is taking over
284 # stop here if subapp is taking over
284 return
285 return
285 cl_config = self.config
286 cl_config = self.config
286 self.init_profile_dir()
287 self.init_profile_dir()
287 self.init_config_files()
288 self.init_config_files()
288 self.load_config_file()
289 self.load_config_file()
289 # enforce cl-opts override configfile opts:
290 # enforce cl-opts override configfile opts:
290 self.update_config(cl_config)
291 self.update_config(cl_config)
291
292
General Comments 0
You need to be logged in to leave comments. Login now