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