##// END OF EJS Templates
default config files are automatically generated...
MinRK -
Show More
@@ -1,379 +1,394 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 from copy import deepcopy
23 import logging
22 import logging
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 from copy import deepcopy
26
27
27 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.loader import (
29 from IPython.config.loader import (
29 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 )
31 )
31
32
32 from IPython.utils.traitlets import (
33 from IPython.utils.traitlets import (
33 Unicode, List, Int, Enum, Dict, Instance
34 Unicode, List, Int, Enum, Dict, Instance
34 )
35 )
35 from IPython.utils.importstring import import_item
36 from IPython.utils.importstring import import_item
36 from IPython.utils.text import indent, wrap_paragraphs, dedent
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # function for re-wrapping a helpstring
40 # function for re-wrapping a helpstring
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
43 # Descriptions for the various sections
44 # Descriptions for the various sections
44 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
45
46
46 flag_description = """
47 flag_description = """
47 Flags are command-line arguments passed as '--<flag>'.
48 Flags are command-line arguments passed as '--<flag>'.
48 These take no parameters, unlike regular key-value arguments.
49 These take no parameters, unlike regular key-value arguments.
49 They are typically used for setting boolean flags, or enabling
50 They are typically used for setting boolean flags, or enabling
50 modes that involve setting multiple options together.
51 modes that involve setting multiple options together.
51
52
52 Flags *always* begin with '--', never just one '-'.
53 Flags *always* begin with '--', never just one '-'.
53 """.strip() # trim newlines of front and back
54 """.strip() # trim newlines of front and back
54
55
55 alias_description = """
56 alias_description = """
56 These are commonly set parameters, given abbreviated aliases for convenience.
57 These are commonly set parameters, given abbreviated aliases for convenience.
57 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
58 <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.
59 """.strip() # trim newlines of front and back
60 """.strip() # trim newlines of front and back
60
61
61 keyvalue_description = """
62 keyvalue_description = """
62 Parameters are set from command-line arguments of the form:
63 Parameters are set from command-line arguments of the form:
63 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
64 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.
65 `C.a='range(3)'` For setting C.a=[0,1,2]
66 `C.a='range(3)'` For setting C.a=[0,1,2]
66 """.strip() # trim newlines of front and back
67 """.strip() # trim newlines of front and back
67
68
68 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
69 # Application class
70 # Application class
70 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
71
72
72
73
73 class ApplicationError(Exception):
74 class ApplicationError(Exception):
74 pass
75 pass
75
76
76
77
77 class Application(SingletonConfigurable):
78 class Application(SingletonConfigurable):
78 """A singleton application with full configuration support."""
79 """A singleton application with full configuration support."""
79
80
80 # 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
81 # line application
82 # line application
82 name = Unicode(u'application')
83 name = Unicode(u'application')
83
84
84 # The description of the application that is printed at the beginning
85 # The description of the application that is printed at the beginning
85 # of the help.
86 # of the help.
86 description = Unicode(u'This is an application.')
87 description = Unicode(u'This is an application.')
87 # default section descriptions
88 # default section descriptions
88 flag_description = Unicode(flag_description)
89 flag_description = Unicode(flag_description)
89 alias_description = Unicode(alias_description)
90 alias_description = Unicode(alias_description)
90 keyvalue_description = Unicode(keyvalue_description)
91 keyvalue_description = Unicode(keyvalue_description)
91
92
92
93
93 # A sequence of Configurable subclasses whose config=True attributes will
94 # A sequence of Configurable subclasses whose config=True attributes will
94 # be exposed at the command line.
95 # be exposed at the command line.
95 classes = List([])
96 classes = List([])
96
97
97 # The version string of this application.
98 # The version string of this application.
98 version = Unicode(u'0.0')
99 version = Unicode(u'0.0')
99
100
100 # The log level for the application
101 # The log level for the application
101 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
102 config=True,
104 config=True,
103 help="Set the log level.")
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
107 if isinstance(new, basestring):
108 self.log_level = getattr(logging, new)
104
109
105 # the alias map for configurables
110 # the alias map for configurables
106 aliases = Dict(dict(log_level='Application.log_level'))
111 aliases = Dict(dict(log_level='Application.log_level'))
107
112
108 # flags for loading Configurables or store_const style flags
113 # flags for loading Configurables or store_const style flags
109 # flags are loaded from this dict by '--key' flags
114 # flags are loaded from this dict by '--key' flags
110 # this must be a dict of two-tuples, the first element being the Config/dict
115 # this must be a dict of two-tuples, the first element being the Config/dict
111 # and the second being the help string for the flag
116 # and the second being the help string for the flag
112 flags = Dict()
117 flags = Dict()
113 def _flags_changed(self, name, old, new):
118 def _flags_changed(self, name, old, new):
114 """ensure flags dict is valid"""
119 """ensure flags dict is valid"""
115 for key,value in new.iteritems():
120 for key,value in new.iteritems():
116 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
121 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
117 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
122 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
118 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
123 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
119
124
120
125
121 # subcommands for launching other applications
126 # subcommands for launching other applications
122 # if this is not empty, this will be a parent Application
127 # if this is not empty, this will be a parent Application
123 # this must be a dict of two-tuples,
128 # this must be a dict of two-tuples,
124 # the first element being the application class/import string
129 # the first element being the application class/import string
125 # and the second being the help string for the subcommand
130 # and the second being the help string for the subcommand
126 subcommands = Dict()
131 subcommands = Dict()
127 # parse_command_line will initialize a subapp, if requested
132 # parse_command_line will initialize a subapp, if requested
128 subapp = Instance('IPython.config.application.Application', allow_none=True)
133 subapp = Instance('IPython.config.application.Application', allow_none=True)
129
134
130 # extra command-line arguments that don't set config values
135 # extra command-line arguments that don't set config values
131 extra_args = List(Unicode)
136 extra_args = List(Unicode)
132
137
133
138
134 def __init__(self, **kwargs):
139 def __init__(self, **kwargs):
135 SingletonConfigurable.__init__(self, **kwargs)
140 SingletonConfigurable.__init__(self, **kwargs)
136 # Add my class to self.classes so my attributes appear in command line
141 # Add my class to self.classes so my attributes appear in command line
137 # options.
142 # options.
138 self.classes.insert(0, self.__class__)
143 self.classes.insert(0, self.__class__)
139
144
140 self.init_logging()
145 self.init_logging()
141
146
142 def _config_changed(self, name, old, new):
147 def _config_changed(self, name, old, new):
143 SingletonConfigurable._config_changed(self, name, old, new)
148 SingletonConfigurable._config_changed(self, name, old, new)
144 self.log.debug('Config changed:')
149 self.log.debug('Config changed:')
145 self.log.debug(repr(new))
150 self.log.debug(repr(new))
146
151
147 def init_logging(self):
152 def init_logging(self):
148 """Start logging for this application.
153 """Start logging for this application.
149
154
150 The default is to log to stdout using a StreaHandler. The log level
155 The default is to log to stdout using a StreaHandler. The log level
151 starts at loggin.WARN, but this can be adjusted by setting the
156 starts at loggin.WARN, but this can be adjusted by setting the
152 ``log_level`` attribute.
157 ``log_level`` attribute.
153 """
158 """
154 self.log = logging.getLogger(self.__class__.__name__)
159 self.log = logging.getLogger(self.__class__.__name__)
155 self.log.setLevel(self.log_level)
160 self.log.setLevel(self.log_level)
156 self._log_handler = logging.StreamHandler()
161 self._log_handler = logging.StreamHandler()
157 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
162 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
158 self._log_handler.setFormatter(self._log_formatter)
163 self._log_handler.setFormatter(self._log_formatter)
159 self.log.addHandler(self._log_handler)
164 self.log.addHandler(self._log_handler)
160
165
161 def initialize(self, argv=None):
166 def initialize(self, argv=None):
162 """Do the basic steps to configure me.
167 """Do the basic steps to configure me.
163
168
164 Override in subclasses.
169 Override in subclasses.
165 """
170 """
166 self.parse_command_line(argv)
171 self.parse_command_line(argv)
167
172
168
173
169 def start(self):
174 def start(self):
170 """Start the app mainloop.
175 """Start the app mainloop.
171
176
172 Override in subclasses.
177 Override in subclasses.
173 """
178 """
174 if self.subapp is not None:
179 if self.subapp is not None:
175 return self.subapp.start()
180 return self.subapp.start()
176
181
177 def _log_level_changed(self, name, old, new):
182 def _log_level_changed(self, name, old, new):
178 """Adjust the log level when log_level is set."""
183 """Adjust the log level when log_level is set."""
179 self.log.setLevel(new)
184 self.log.setLevel(new)
180
185
181 def print_alias_help(self):
186 def print_alias_help(self):
182 """Print the alias part of the help."""
187 """Print the alias part of the help."""
183 if not self.aliases:
188 if not self.aliases:
184 return
189 return
185
190
186 lines = ['Aliases']
191 lines = ['Aliases']
187 lines.append('-'*len(lines[0]))
192 lines.append('-'*len(lines[0]))
188 lines.append('')
193 lines.append('')
189 for p in wrap_paragraphs(self.alias_description):
194 for p in wrap_paragraphs(self.alias_description):
190 lines.append(p)
195 lines.append(p)
191 lines.append('')
196 lines.append('')
192
197
193 classdict = {}
198 classdict = {}
194 for cls in self.classes:
199 for cls in self.classes:
195 # include all parents (up to, but excluding Configurable) in available names
200 # include all parents (up to, but excluding Configurable) in available names
196 for c in cls.mro()[:-3]:
201 for c in cls.mro()[:-3]:
197 classdict[c.__name__] = c
202 classdict[c.__name__] = c
198
203
199 for alias, longname in self.aliases.iteritems():
204 for alias, longname in self.aliases.iteritems():
200 classname, traitname = longname.split('.',1)
205 classname, traitname = longname.split('.',1)
201 cls = classdict[classname]
206 cls = classdict[classname]
202
207
203 trait = cls.class_traits(config=True)[traitname]
208 trait = cls.class_traits(config=True)[traitname]
204 help = cls.class_get_trait_help(trait)
209 help = cls.class_get_trait_help(trait)
205 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
210 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
206 lines.append(help)
211 lines.append(help)
207 lines.append('')
212 lines.append('')
208 print '\n'.join(lines)
213 print '\n'.join(lines)
209
214
210 def print_flag_help(self):
215 def print_flag_help(self):
211 """Print the flag part of the help."""
216 """Print the flag part of the help."""
212 if not self.flags:
217 if not self.flags:
213 return
218 return
214
219
215 lines = ['Flags']
220 lines = ['Flags']
216 lines.append('-'*len(lines[0]))
221 lines.append('-'*len(lines[0]))
217 lines.append('')
222 lines.append('')
218 for p in wrap_paragraphs(self.flag_description):
223 for p in wrap_paragraphs(self.flag_description):
219 lines.append(p)
224 lines.append(p)
220 lines.append('')
225 lines.append('')
221
226
222 for m, (cfg,help) in self.flags.iteritems():
227 for m, (cfg,help) in self.flags.iteritems():
223 lines.append('--'+m)
228 lines.append('--'+m)
224 lines.append(indent(dedent(help.strip())))
229 lines.append(indent(dedent(help.strip())))
225 lines.append('')
230 lines.append('')
226 print '\n'.join(lines)
231 print '\n'.join(lines)
227
232
228 def print_subcommands(self):
233 def print_subcommands(self):
229 """Print the subcommand part of the help."""
234 """Print the subcommand part of the help."""
230 if not self.subcommands:
235 if not self.subcommands:
231 return
236 return
232
237
233 lines = ["Subcommands"]
238 lines = ["Subcommands"]
234 lines.append('-'*len(lines[0]))
239 lines.append('-'*len(lines[0]))
235 for subc, (cls,help) in self.subcommands.iteritems():
240 for subc, (cls,help) in self.subcommands.iteritems():
236 lines.append("%s : %s"%(subc, cls))
241 lines.append("%s : %s"%(subc, cls))
237 if help:
242 if help:
238 lines.append(indent(dedent(help.strip())))
243 lines.append(indent(dedent(help.strip())))
239 lines.append('')
244 lines.append('')
240 print '\n'.join(lines)
245 print '\n'.join(lines)
241
246
242 def print_help(self, classes=False):
247 def print_help(self, classes=False):
243 """Print the help for each Configurable class in self.classes.
248 """Print the help for each Configurable class in self.classes.
244
249
245 If classes=False (the default), only flags and aliases are printed.
250 If classes=False (the default), only flags and aliases are printed.
246 """
251 """
247 self.print_subcommands()
252 self.print_subcommands()
248 self.print_flag_help()
253 self.print_flag_help()
249 self.print_alias_help()
254 self.print_alias_help()
250
255
251 if classes:
256 if classes:
252 if self.classes:
257 if self.classes:
253 print "Class parameters"
258 print "Class parameters"
254 print "----------------"
259 print "----------------"
255 print
260 print
256 for p in wrap_paragraphs(self.keyvalue_description):
261 for p in wrap_paragraphs(self.keyvalue_description):
257 print p
262 print p
258 print
263 print
259
264
260 for cls in self.classes:
265 for cls in self.classes:
261 cls.class_print_help()
266 cls.class_print_help()
262 print
267 print
263 else:
268 else:
264 print "To see all available configurables, use `--help-all`"
269 print "To see all available configurables, use `--help-all`"
265 print
270 print
266
271
267 def print_description(self):
272 def print_description(self):
268 """Print the application description."""
273 """Print the application description."""
269 for p in wrap_paragraphs(self.description):
274 for p in wrap_paragraphs(self.description):
270 print p
275 print p
271 print
276 print
272
277
273 def print_version(self):
278 def print_version(self):
274 """Print the version string."""
279 """Print the version string."""
275 print self.version
280 print self.version
276
281
277 def update_config(self, config):
282 def update_config(self, config):
278 """Fire the traits events when the config is updated."""
283 """Fire the traits events when the config is updated."""
279 # Save a copy of the current config.
284 # Save a copy of the current config.
280 newconfig = deepcopy(self.config)
285 newconfig = deepcopy(self.config)
281 # Merge the new config into the current one.
286 # Merge the new config into the current one.
282 newconfig._merge(config)
287 newconfig._merge(config)
283 # Save the combined config as self.config, which triggers the traits
288 # Save the combined config as self.config, which triggers the traits
284 # events.
289 # events.
285 self.config = newconfig
290 self.config = newconfig
286
291
287 def initialize_subcommand(self, subc, argv=None):
292 def initialize_subcommand(self, subc, argv=None):
288 """Initialize a subcommand with argv."""
293 """Initialize a subcommand with argv."""
289 subapp,help = self.subcommands.get(subc)
294 subapp,help = self.subcommands.get(subc)
290
295
291 if isinstance(subapp, basestring):
296 if isinstance(subapp, basestring):
292 subapp = import_item(subapp)
297 subapp = import_item(subapp)
293
298
294 # clear existing instances
299 # clear existing instances
295 self.__class__.clear_instance()
300 self.__class__.clear_instance()
296 # instantiate
301 # instantiate
297 self.subapp = subapp.instance()
302 self.subapp = subapp.instance()
298 # and initialize subapp
303 # and initialize subapp
299 self.subapp.initialize(argv)
304 self.subapp.initialize(argv)
300
305
301 def parse_command_line(self, argv=None):
306 def parse_command_line(self, argv=None):
302 """Parse the command line arguments."""
307 """Parse the command line arguments."""
303 argv = sys.argv[1:] if argv is None else argv
308 argv = sys.argv[1:] if argv is None else argv
304
309
305 if self.subcommands and len(argv) > 0:
310 if self.subcommands and len(argv) > 0:
306 # we have subcommands, and one may have been specified
311 # we have subcommands, and one may have been specified
307 subc, subargv = argv[0], argv[1:]
312 subc, subargv = argv[0], argv[1:]
308 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
313 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
309 # it's a subcommand, and *not* a flag or class parameter
314 # it's a subcommand, and *not* a flag or class parameter
310 return self.initialize_subcommand(subc, subargv)
315 return self.initialize_subcommand(subc, subargv)
311
316
312 if '-h' in argv or '--help' in argv or '--help-all' in argv:
317 if '-h' in argv or '--help' in argv or '--help-all' in argv:
313 self.print_description()
318 self.print_description()
314 self.print_help('--help-all' in argv)
319 self.print_help('--help-all' in argv)
315 self.exit(0)
320 self.exit(0)
316
321
317 if '--version' in argv:
322 if '--version' in argv:
318 self.print_version()
323 self.print_version()
319 self.exit(0)
324 self.exit(0)
320
325
321 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
326 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
322 flags=self.flags)
327 flags=self.flags)
323 try:
328 try:
324 config = loader.load_config()
329 config = loader.load_config()
325 except ArgumentError as e:
330 except ArgumentError as e:
326 self.log.fatal(str(e))
331 self.log.fatal(str(e))
327 self.print_description()
332 self.print_description()
328 self.print_help()
333 self.print_help()
329 self.exit(1)
334 self.exit(1)
330 self.update_config(config)
335 self.update_config(config)
331 # store unparsed args in extra_args
336 # store unparsed args in extra_args
332 self.extra_args = loader.extra_args
337 self.extra_args = loader.extra_args
333
338
334 def load_config_file(self, filename, path=None):
339 def load_config_file(self, filename, path=None):
335 """Load a .py based config file by filename and path."""
340 """Load a .py based config file by filename and path."""
336 loader = PyFileConfigLoader(filename, path=path)
341 loader = PyFileConfigLoader(filename, path=path)
337 config = loader.load_config()
342 config = loader.load_config()
338 self.update_config(config)
343 self.update_config(config)
339
344
345 def generate_config_file(self):
346 """generate default config file from Configurables"""
347 lines = ["# Configuration file for %s."%self.name]
348 lines.append('')
349 lines.append('c = get_config()')
350 lines.append('')
351 for cls in self.classes:
352 lines.append(cls.class_config_section())
353 return '\n'.join(lines)
354
340 def exit(self, exit_status=0):
355 def exit(self, exit_status=0):
341 self.log.debug("Exiting application: %s" % self.name)
356 self.log.debug("Exiting application: %s" % self.name)
342 sys.exit(exit_status)
357 sys.exit(exit_status)
343
358
344 #-----------------------------------------------------------------------------
359 #-----------------------------------------------------------------------------
345 # utility functions, for convenience
360 # utility functions, for convenience
346 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
347
362
348 def boolean_flag(name, configurable, set_help='', unset_help=''):
363 def boolean_flag(name, configurable, set_help='', unset_help=''):
349 """Helper for building basic --trait, --no-trait flags.
364 """Helper for building basic --trait, --no-trait flags.
350
365
351 Parameters
366 Parameters
352 ----------
367 ----------
353
368
354 name : str
369 name : str
355 The name of the flag.
370 The name of the flag.
356 configurable : str
371 configurable : str
357 The 'Class.trait' string of the trait to be set/unset with the flag
372 The 'Class.trait' string of the trait to be set/unset with the flag
358 set_help : unicode
373 set_help : unicode
359 help string for --name flag
374 help string for --name flag
360 unset_help : unicode
375 unset_help : unicode
361 help string for --no-name flag
376 help string for --no-name flag
362
377
363 Returns
378 Returns
364 -------
379 -------
365
380
366 cfg : dict
381 cfg : dict
367 A dict with two keys: 'name', and 'no-name', for setting and unsetting
382 A dict with two keys: 'name', and 'no-name', for setting and unsetting
368 the trait, respectively.
383 the trait, respectively.
369 """
384 """
370 # default helpstrings
385 # default helpstrings
371 set_help = set_help or "set %s=True"%configurable
386 set_help = set_help or "set %s=True"%configurable
372 unset_help = unset_help or "set %s=False"%configurable
387 unset_help = unset_help or "set %s=False"%configurable
373
388
374 cls,trait = configurable.split('.')
389 cls,trait = configurable.split('.')
375
390
376 setter = {cls : {trait : True}}
391 setter = {cls : {trait : True}}
377 unsetter = {cls : {trait : False}}
392 unsetter = {cls : {trait : False}}
378 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
393 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
379
394
@@ -1,283 +1,315 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A base class for objects that are configurable.
4 A base class for objects that are configurable.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 * Min RK
10 * Min RK
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import datetime
24 import datetime
25 from copy import deepcopy
25 from copy import deepcopy
26
26
27 from loader import Config
27 from loader import Config
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from IPython.utils.text import indent, wrap_paragraphs
29 from IPython.utils.text import indent, wrap_paragraphs
30
30
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Helper classes for Configurables
33 # Helper classes for Configurables
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class ConfigurableError(Exception):
37 class ConfigurableError(Exception):
38 pass
38 pass
39
39
40
40
41 class MultipleInstanceError(ConfigurableError):
41 class MultipleInstanceError(ConfigurableError):
42 pass
42 pass
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Configurable implementation
45 # Configurable implementation
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class Configurable(HasTraits):
48 class Configurable(HasTraits):
49
49
50 config = Instance(Config,(),{})
50 config = Instance(Config,(),{})
51 created = None
51 created = None
52
52
53 def __init__(self, **kwargs):
53 def __init__(self, **kwargs):
54 """Create a configurable given a config config.
54 """Create a configurable given a config config.
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 config : Config
58 config : Config
59 If this is empty, default values are used. If config is a
59 If this is empty, default values are used. If config is a
60 :class:`Config` instance, it will be used to configure the
60 :class:`Config` instance, it will be used to configure the
61 instance.
61 instance.
62
62
63 Notes
63 Notes
64 -----
64 -----
65 Subclasses of Configurable must call the :meth:`__init__` method of
65 Subclasses of Configurable must call the :meth:`__init__` method of
66 :class:`Configurable` *before* doing anything else and using
66 :class:`Configurable` *before* doing anything else and using
67 :func:`super`::
67 :func:`super`::
68
68
69 class MyConfigurable(Configurable):
69 class MyConfigurable(Configurable):
70 def __init__(self, config=None):
70 def __init__(self, config=None):
71 super(MyConfigurable, self).__init__(config)
71 super(MyConfigurable, self).__init__(config)
72 # Then any other code you need to finish initialization.
72 # Then any other code you need to finish initialization.
73
73
74 This ensures that instances will be configured properly.
74 This ensures that instances will be configured properly.
75 """
75 """
76 config = kwargs.pop('config', None)
76 config = kwargs.pop('config', None)
77 if config is not None:
77 if config is not None:
78 # We used to deepcopy, but for now we are trying to just save
78 # We used to deepcopy, but for now we are trying to just save
79 # by reference. This *could* have side effects as all components
79 # by reference. This *could* have side effects as all components
80 # will share config. In fact, I did find such a side effect in
80 # will share config. In fact, I did find such a side effect in
81 # _config_changed below. If a config attribute value was a mutable type
81 # _config_changed below. If a config attribute value was a mutable type
82 # all instances of a component were getting the same copy, effectively
82 # all instances of a component were getting the same copy, effectively
83 # making that a class attribute.
83 # making that a class attribute.
84 # self.config = deepcopy(config)
84 # self.config = deepcopy(config)
85 self.config = config
85 self.config = config
86 # This should go second so individual keyword arguments override
86 # This should go second so individual keyword arguments override
87 # the values in config.
87 # the values in config.
88 super(Configurable, self).__init__(**kwargs)
88 super(Configurable, self).__init__(**kwargs)
89 self.created = datetime.datetime.now()
89 self.created = datetime.datetime.now()
90
90
91 #-------------------------------------------------------------------------
91 #-------------------------------------------------------------------------
92 # Static trait notifiations
92 # Static trait notifiations
93 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
94
94
95 def _config_changed(self, name, old, new):
95 def _config_changed(self, name, old, new):
96 """Update all the class traits having ``config=True`` as metadata.
96 """Update all the class traits having ``config=True`` as metadata.
97
97
98 For any class trait with a ``config`` metadata attribute that is
98 For any class trait with a ``config`` metadata attribute that is
99 ``True``, we update the trait with the value of the corresponding
99 ``True``, we update the trait with the value of the corresponding
100 config entry.
100 config entry.
101 """
101 """
102 # Get all traits with a config metadata entry that is True
102 # Get all traits with a config metadata entry that is True
103 traits = self.traits(config=True)
103 traits = self.traits(config=True)
104
104
105 # We auto-load config section for this class as well as any parent
105 # We auto-load config section for this class as well as any parent
106 # classes that are Configurable subclasses. This starts with Configurable
106 # classes that are Configurable subclasses. This starts with Configurable
107 # and works down the mro loading the config for each section.
107 # and works down the mro loading the config for each section.
108 section_names = [cls.__name__ for cls in \
108 section_names = [cls.__name__ for cls in \
109 reversed(self.__class__.__mro__) if
109 reversed(self.__class__.__mro__) if
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
110 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
111
111
112 for sname in section_names:
112 for sname in section_names:
113 # Don't do a blind getattr as that would cause the config to
113 # Don't do a blind getattr as that would cause the config to
114 # dynamically create the section with name self.__class__.__name__.
114 # dynamically create the section with name self.__class__.__name__.
115 if new._has_section(sname):
115 if new._has_section(sname):
116 my_config = new[sname]
116 my_config = new[sname]
117 for k, v in traits.iteritems():
117 for k, v in traits.iteritems():
118 # Don't allow traitlets with config=True to start with
118 # Don't allow traitlets with config=True to start with
119 # uppercase. Otherwise, they are confused with Config
119 # uppercase. Otherwise, they are confused with Config
120 # subsections. But, developers shouldn't have uppercase
120 # subsections. But, developers shouldn't have uppercase
121 # attributes anyways! (PEP 6)
121 # attributes anyways! (PEP 6)
122 if k[0].upper()==k[0] and not k.startswith('_'):
122 if k[0].upper()==k[0] and not k.startswith('_'):
123 raise ConfigurableError('Configurable traitlets with '
123 raise ConfigurableError('Configurable traitlets with '
124 'config=True must start with a lowercase so they are '
124 'config=True must start with a lowercase so they are '
125 'not confused with Config subsections: %s.%s' % \
125 'not confused with Config subsections: %s.%s' % \
126 (self.__class__.__name__, k))
126 (self.__class__.__name__, k))
127 try:
127 try:
128 # Here we grab the value from the config
128 # Here we grab the value from the config
129 # If k has the naming convention of a config
129 # If k has the naming convention of a config
130 # section, it will be auto created.
130 # section, it will be auto created.
131 config_value = my_config[k]
131 config_value = my_config[k]
132 except KeyError:
132 except KeyError:
133 pass
133 pass
134 else:
134 else:
135 # print "Setting %s.%s from %s.%s=%r" % \
135 # print "Setting %s.%s from %s.%s=%r" % \
136 # (self.__class__.__name__,k,sname,k,config_value)
136 # (self.__class__.__name__,k,sname,k,config_value)
137 # We have to do a deepcopy here if we don't deepcopy the entire
137 # We have to do a deepcopy here if we don't deepcopy the entire
138 # config object. If we don't, a mutable config_value will be
138 # config object. If we don't, a mutable config_value will be
139 # shared by all instances, effectively making it a class attribute.
139 # shared by all instances, effectively making it a class attribute.
140 setattr(self, k, deepcopy(config_value))
140 setattr(self, k, deepcopy(config_value))
141
141
142 @classmethod
142 @classmethod
143 def class_get_help(cls):
143 def class_get_help(cls):
144 """Get the help string for this class in ReST format."""
144 """Get the help string for this class in ReST format."""
145 cls_traits = cls.class_traits(config=True)
145 cls_traits = cls.class_traits(config=True)
146 final_help = []
146 final_help = []
147 final_help.append(u'%s options' % cls.__name__)
147 final_help.append(u'%s options' % cls.__name__)
148 final_help.append(len(final_help[0])*u'-')
148 final_help.append(len(final_help[0])*u'-')
149 for k,v in cls.class_traits(config=True).iteritems():
149 for k,v in cls.class_traits(config=True).iteritems():
150 help = cls.class_get_trait_help(v)
150 help = cls.class_get_trait_help(v)
151 final_help.append(help)
151 final_help.append(help)
152 return '\n'.join(final_help)
152 return '\n'.join(final_help)
153
153
154 @classmethod
154 @classmethod
155 def class_get_trait_help(cls, trait):
155 def class_get_trait_help(cls, trait):
156 """Get the help string for a single trait."""
156 """Get the help string for a single trait."""
157 lines = []
157 lines = []
158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
159 lines.append(header)
159 lines.append(header)
160 try:
160 try:
161 dvr = repr(trait.get_default_value())
161 dvr = repr(trait.get_default_value())
162 except Exception:
162 except Exception:
163 dvr = None # ignore defaults we can't construct
163 dvr = None # ignore defaults we can't construct
164 if dvr is not None:
164 if dvr is not None:
165 if len(dvr) > 64:
165 if len(dvr) > 64:
166 dvr = dvr[:61]+'...'
166 dvr = dvr[:61]+'...'
167 lines.append(indent('Default: %s'%dvr, 4))
167 lines.append(indent('Default: %s'%dvr, 4))
168 if 'Enum' in trait.__class__.__name__:
168 if 'Enum' in trait.__class__.__name__:
169 # include Enum choices
169 # include Enum choices
170 lines.append(indent('Choices: %r'%(trait.values,)))
170 lines.append(indent('Choices: %r'%(trait.values,)))
171
171
172 help = trait.get_metadata('help')
172 help = trait.get_metadata('help')
173 if help is not None:
173 if help is not None:
174 help = '\n'.join(wrap_paragraphs(help, 76))
174 help = '\n'.join(wrap_paragraphs(help, 76))
175 lines.append(indent(help, 4))
175 lines.append(indent(help, 4))
176 return '\n'.join(lines)
176 return '\n'.join(lines)
177
177
178 @classmethod
178 @classmethod
179 def class_print_help(cls):
179 def class_print_help(cls):
180 """Get the help string for a single trait and print it."""
180 """Get the help string for a single trait and print it."""
181 print cls.class_get_help()
181 print cls.class_get_help()
182
182
183 @classmethod
184 def class_config_section(cls):
185 """Get the config class config section"""
186 def c(s):
187 """return a commented, wrapped block."""
188 s = '\n\n'.join(wrap_paragraphs(s, 78))
189
190 return '# ' + s.replace('\n', '\n# ')
191
192 # section header
193 breaker = '#' + '-'*78
194 s = "# %s configuration"%cls.__name__
195 lines = [breaker, s, breaker, '']
196 # get the description trait
197 desc = cls.class_traits().get('description')
198 if desc:
199 desc = desc.default_value
200 else:
201 # no description trait, use __doc__
202 desc = getattr(cls, '__doc__', '')
203 if desc:
204 lines.append(c(desc))
205 lines.append('')
206
207 for name,trait in cls.class_traits(config=True).iteritems():
208 help = trait.get_metadata('help') or ''
209 lines.append(c(help))
210 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
211 lines.append('')
212 return '\n'.join(lines)
213
214
183
215
184 class SingletonConfigurable(Configurable):
216 class SingletonConfigurable(Configurable):
185 """A configurable that only allows one instance.
217 """A configurable that only allows one instance.
186
218
187 This class is for classes that should only have one instance of itself
219 This class is for classes that should only have one instance of itself
188 or *any* subclass. To create and retrieve such a class use the
220 or *any* subclass. To create and retrieve such a class use the
189 :meth:`SingletonConfigurable.instance` method.
221 :meth:`SingletonConfigurable.instance` method.
190 """
222 """
191
223
192 _instance = None
224 _instance = None
193
225
194 @classmethod
226 @classmethod
195 def _walk_mro(cls):
227 def _walk_mro(cls):
196 """Walk the cls.mro() for parent classes that are also singletons
228 """Walk the cls.mro() for parent classes that are also singletons
197
229
198 For use in instance()
230 For use in instance()
199 """
231 """
200
232
201 for subclass in cls.mro():
233 for subclass in cls.mro():
202 if issubclass(cls, subclass) and \
234 if issubclass(cls, subclass) and \
203 issubclass(subclass, SingletonConfigurable) and \
235 issubclass(subclass, SingletonConfigurable) and \
204 subclass != SingletonConfigurable:
236 subclass != SingletonConfigurable:
205 yield subclass
237 yield subclass
206
238
207 @classmethod
239 @classmethod
208 def clear_instance(cls):
240 def clear_instance(cls):
209 """unset _instance for this class and singleton parents.
241 """unset _instance for this class and singleton parents.
210 """
242 """
211 if not cls.initialized():
243 if not cls.initialized():
212 return
244 return
213 for subclass in cls._walk_mro():
245 for subclass in cls._walk_mro():
214 if isinstance(subclass._instance, cls):
246 if isinstance(subclass._instance, cls):
215 # only clear instances that are instances
247 # only clear instances that are instances
216 # of the calling class
248 # of the calling class
217 subclass._instance = None
249 subclass._instance = None
218
250
219 @classmethod
251 @classmethod
220 def instance(cls, *args, **kwargs):
252 def instance(cls, *args, **kwargs):
221 """Returns a global instance of this class.
253 """Returns a global instance of this class.
222
254
223 This method create a new instance if none have previously been created
255 This method create a new instance if none have previously been created
224 and returns a previously created instance is one already exists.
256 and returns a previously created instance is one already exists.
225
257
226 The arguments and keyword arguments passed to this method are passed
258 The arguments and keyword arguments passed to this method are passed
227 on to the :meth:`__init__` method of the class upon instantiation.
259 on to the :meth:`__init__` method of the class upon instantiation.
228
260
229 Examples
261 Examples
230 --------
262 --------
231
263
232 Create a singleton class using instance, and retrieve it::
264 Create a singleton class using instance, and retrieve it::
233
265
234 >>> from IPython.config.configurable import SingletonConfigurable
266 >>> from IPython.config.configurable import SingletonConfigurable
235 >>> class Foo(SingletonConfigurable): pass
267 >>> class Foo(SingletonConfigurable): pass
236 >>> foo = Foo.instance()
268 >>> foo = Foo.instance()
237 >>> foo == Foo.instance()
269 >>> foo == Foo.instance()
238 True
270 True
239
271
240 Create a subclass that is retrived using the base class instance::
272 Create a subclass that is retrived using the base class instance::
241
273
242 >>> class Bar(SingletonConfigurable): pass
274 >>> class Bar(SingletonConfigurable): pass
243 >>> class Bam(Bar): pass
275 >>> class Bam(Bar): pass
244 >>> bam = Bam.instance()
276 >>> bam = Bam.instance()
245 >>> bam == Bar.instance()
277 >>> bam == Bar.instance()
246 True
278 True
247 """
279 """
248 # Create and save the instance
280 # Create and save the instance
249 if cls._instance is None:
281 if cls._instance is None:
250 inst = cls(*args, **kwargs)
282 inst = cls(*args, **kwargs)
251 # Now make sure that the instance will also be returned by
283 # Now make sure that the instance will also be returned by
252 # parent classes' _instance attribute.
284 # parent classes' _instance attribute.
253 for subclass in cls._walk_mro():
285 for subclass in cls._walk_mro():
254 subclass._instance = inst
286 subclass._instance = inst
255
287
256 if isinstance(cls._instance, cls):
288 if isinstance(cls._instance, cls):
257 return cls._instance
289 return cls._instance
258 else:
290 else:
259 raise MultipleInstanceError(
291 raise MultipleInstanceError(
260 'Multiple incompatible subclass instances of '
292 'Multiple incompatible subclass instances of '
261 '%s are being created.' % cls.__name__
293 '%s are being created.' % cls.__name__
262 )
294 )
263
295
264 @classmethod
296 @classmethod
265 def initialized(cls):
297 def initialized(cls):
266 """Has an instance been created?"""
298 """Has an instance been created?"""
267 return hasattr(cls, "_instance") and cls._instance is not None
299 return hasattr(cls, "_instance") and cls._instance is not None
268
300
269
301
270 class LoggingConfigurable(Configurable):
302 class LoggingConfigurable(Configurable):
271 """A parent class for Configurables that log.
303 """A parent class for Configurables that log.
272
304
273 Subclasses have a log trait, and the default behavior
305 Subclasses have a log trait, and the default behavior
274 is to get the logger from the currently running Application
306 is to get the logger from the currently running Application
275 via Application.instance().log.
307 via Application.instance().log.
276 """
308 """
277
309
278 log = Instance('logging.Logger')
310 log = Instance('logging.Logger')
279 def _log_default(self):
311 def _log_default(self):
280 from IPython.config.application import Application
312 from IPython.config.application import Application
281 return Application.instance().log
313 return Application.instance().log
282
314
283
315
@@ -1,259 +1,290 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 )
57 )
58
58
59 base_flags = dict(
59 base_flags = dict(
60 debug = ({'Application' : {'log_level' : logging.DEBUG}},
60 debug = ({'Application' : {'log_level' : logging.DEBUG}},
61 "set log level to logging.DEBUG (maximize logging output)"),
61 "set log level to logging.DEBUG (maximize logging output)"),
62 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
62 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
63 "set log level to logging.CRITICAL (minimize logging output)"),
63 "set log level to logging.CRITICAL (minimize logging output)"),
64 init = ({'BaseIPythonApplication' : {
64 init = ({'BaseIPythonApplication' : {
65 'copy_config_files' : True,
65 'copy_config_files' : True,
66 'auto_create' : True}
66 'auto_create' : True}
67 }, "Initialize profile with default config files")
67 }, "Initialize profile with default config files")
68 )
68 )
69
69
70
70
71 class BaseIPythonApplication(Application):
71 class BaseIPythonApplication(Application):
72
72
73 name = Unicode(u'ipython')
73 name = Unicode(u'ipython')
74 description = Unicode(u'IPython: an enhanced interactive Python shell.')
74 description = Unicode(u'IPython: an enhanced interactive Python shell.')
75 version = Unicode(release.version)
75 version = Unicode(release.version)
76
76
77 aliases = Dict(base_aliases)
77 aliases = Dict(base_aliases)
78 flags = Dict(base_flags)
78 flags = Dict(base_flags)
79
79
80 # Track whether the config_file has changed,
80 # Track whether the config_file has changed,
81 # because some logic happens only if we aren't using the default.
81 # because some logic happens only if we aren't using the default.
82 config_file_specified = Bool(False)
82 config_file_specified = Bool(False)
83
83
84 config_file_name = Unicode(u'ipython_config.py')
84 config_file_name = Unicode(u'ipython_config.py')
85 def _config_file_name_default(self):
86 return self.name.replace('-','_') + u'_config.py'
85 def _config_file_name_changed(self, name, old, new):
87 def _config_file_name_changed(self, name, old, new):
86 if new != old:
88 if new != old:
87 self.config_file_specified = True
89 self.config_file_specified = True
88
90
89 # The directory that contains IPython's builtin profiles.
91 # The directory that contains IPython's builtin profiles.
90 builtin_profile_dir = Unicode(
92 builtin_profile_dir = Unicode(
91 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
93 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
92 )
94 )
93
95
94 config_file_paths = List(Unicode)
96 config_file_paths = List(Unicode)
95 def _config_file_paths_default(self):
97 def _config_file_paths_default(self):
96 return [os.getcwdu()]
98 return [os.getcwdu()]
97
99
98 profile = Unicode(u'default', config=True,
100 profile = Unicode(u'default', config=True,
99 help="""The IPython profile to use."""
101 help="""The IPython profile to use."""
100 )
102 )
101 def _profile_changed(self, name, old, new):
103 def _profile_changed(self, name, old, new):
102 self.builtin_profile_dir = os.path.join(
104 self.builtin_profile_dir = os.path.join(
103 get_ipython_package_dir(), u'config', u'profile', new
105 get_ipython_package_dir(), u'config', u'profile', new
104 )
106 )
105
107
106
107 ipython_dir = Unicode(get_ipython_dir(), config=True,
108 ipython_dir = Unicode(get_ipython_dir(), config=True,
108 help="""
109 help="""
109 The name of the IPython directory. This directory is used for logging
110 The name of the IPython directory. This directory is used for logging
110 configuration (through profiles), history storage, etc. The default
111 configuration (through profiles), history storage, etc. The default
111 is usually $HOME/.ipython. This options can also be specified through
112 is usually $HOME/.ipython. This options can also be specified through
112 the environment variable IPYTHON_DIR.
113 the environment variable IPYTHON_DIR.
113 """
114 """
114 )
115 )
115
116
116 overwrite = Bool(False, config=True,
117 overwrite = Bool(False, config=True,
117 help="""Whether to overwrite existing config files when copying""")
118 help="""Whether to overwrite existing config files when copying""")
118 auto_create = Bool(False, config=True,
119 auto_create = Bool(False, config=True,
119 help="""Whether to create profile dir if it doesn't exist""")
120 help="""Whether to create profile dir if it doesn't exist""")
120
121
121 config_files = List(Unicode)
122 config_files = List(Unicode)
122 def _config_files_default(self):
123 def _config_files_default(self):
123 return [u'ipython_config.py']
124 return [u'ipython_config.py']
124
125
125 copy_config_files = Bool(False, config=True,
126 copy_config_files = Bool(False, config=True,
126 help="""Whether to copy the default config files into the profile dir.""")
127 help="""Whether to install the default config files into the profile dir.
128 If a new profile is being created, and IPython contains config files for that
129 profile, then they will be staged into the new directory. Otherwise,
130 default config files will be automatically generated.
131 """)
127
132
128 # The class to use as the crash handler.
133 # The class to use as the crash handler.
129 crash_handler_class = Type(crashhandler.CrashHandler)
134 crash_handler_class = Type(crashhandler.CrashHandler)
130
135
131 def __init__(self, **kwargs):
136 def __init__(self, **kwargs):
132 super(BaseIPythonApplication, self).__init__(**kwargs)
137 super(BaseIPythonApplication, self).__init__(**kwargs)
133 # ensure even default IPYTHON_DIR exists
138 # ensure even default IPYTHON_DIR exists
134 if not os.path.exists(self.ipython_dir):
139 if not os.path.exists(self.ipython_dir):
135 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
140 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
136
141
137 #-------------------------------------------------------------------------
142 #-------------------------------------------------------------------------
138 # Various stages of Application creation
143 # Various stages of Application creation
139 #-------------------------------------------------------------------------
144 #-------------------------------------------------------------------------
140
145
141 def init_crash_handler(self):
146 def init_crash_handler(self):
142 """Create a crash handler, typically setting sys.excepthook to it."""
147 """Create a crash handler, typically setting sys.excepthook to it."""
143 self.crash_handler = self.crash_handler_class(self)
148 self.crash_handler = self.crash_handler_class(self)
144 sys.excepthook = self.crash_handler
149 sys.excepthook = self.crash_handler
145
150
146 def _ipython_dir_changed(self, name, old, new):
151 def _ipython_dir_changed(self, name, old, new):
147 if old in sys.path:
152 if old in sys.path:
148 sys.path.remove(old)
153 sys.path.remove(old)
149 sys.path.append(os.path.abspath(new))
154 sys.path.append(os.path.abspath(new))
150 if not os.path.isdir(new):
155 if not os.path.isdir(new):
151 os.makedirs(new, mode=0777)
156 os.makedirs(new, mode=0777)
152 readme = os.path.join(new, 'README')
157 readme = os.path.join(new, 'README')
153 if not os.path.exists(readme):
158 if not os.path.exists(readme):
154 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
159 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
155 shutil.copy(os.path.join(path, 'README'), readme)
160 shutil.copy(os.path.join(path, 'README'), readme)
156 self.log.debug("IPYTHON_DIR set to: %s" % new)
161 self.log.debug("IPYTHON_DIR set to: %s" % new)
157
162
158 def load_config_file(self, suppress_errors=True):
163 def load_config_file(self, suppress_errors=True):
159 """Load the config file.
164 """Load the config file.
160
165
161 By default, errors in loading config are handled, and a warning
166 By default, errors in loading config are handled, and a warning
162 printed on screen. For testing, the suppress_errors option is set
167 printed on screen. For testing, the suppress_errors option is set
163 to False, so errors will make tests fail.
168 to False, so errors will make tests fail.
164 """
169 """
170 base_config = 'ipython_config.py'
171 self.log.debug("Attempting to load config file: %s" %
172 base_config)
173 try:
174 Application.load_config_file(
175 self,
176 base_config,
177 path=self.config_file_paths
178 )
179 except IOError:
180 # ignore errors loading parent
181 pass
182 if self.config_file_name == base_config:
183 # don't load secondary config
184 return
165 self.log.debug("Attempting to load config file: %s" %
185 self.log.debug("Attempting to load config file: %s" %
166 self.config_file_name)
186 self.config_file_name)
167 try:
187 try:
168 Application.load_config_file(
188 Application.load_config_file(
169 self,
189 self,
170 self.config_file_name,
190 self.config_file_name,
171 path=self.config_file_paths
191 path=self.config_file_paths
172 )
192 )
173 except IOError:
193 except IOError:
174 # Only warn if the default config file was NOT being used.
194 # Only warn if the default config file was NOT being used.
175 if self.config_file_specified:
195 if self.config_file_specified:
176 self.log.warn("Config file not found, skipping: %s" %
196 self.log.warn("Config file not found, skipping: %s" %
177 self.config_file_name)
197 self.config_file_name)
178 except:
198 except:
179 # For testing purposes.
199 # For testing purposes.
180 if not suppress_errors:
200 if not suppress_errors:
181 raise
201 raise
182 self.log.warn("Error loading config file: %s" %
202 self.log.warn("Error loading config file: %s" %
183 self.config_file_name, exc_info=True)
203 self.config_file_name, exc_info=True)
184
204
185 def init_profile_dir(self):
205 def init_profile_dir(self):
186 """initialize the profile dir"""
206 """initialize the profile dir"""
187 try:
207 try:
188 # location explicitly specified:
208 # location explicitly specified:
189 location = self.config.ProfileDir.location
209 location = self.config.ProfileDir.location
190 except AttributeError:
210 except AttributeError:
191 # location not specified, find by profile name
211 # location not specified, find by profile name
192 try:
212 try:
193 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
213 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
194 except ProfileDirError:
214 except ProfileDirError:
195 # not found, maybe create it (always create default profile)
215 # not found, maybe create it (always create default profile)
196 if self.auto_create or self.profile=='default':
216 if self.auto_create or self.profile=='default':
197 try:
217 try:
198 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
218 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
199 except ProfileDirError:
219 except ProfileDirError:
200 self.log.fatal("Could not create profile: %r"%self.profile)
220 self.log.fatal("Could not create profile: %r"%self.profile)
201 self.exit(1)
221 self.exit(1)
202 else:
222 else:
203 self.log.info("Created profile dir: %r"%p.location)
223 self.log.info("Created profile dir: %r"%p.location)
204 else:
224 else:
205 self.log.fatal("Profile %r not found."%self.profile)
225 self.log.fatal("Profile %r not found."%self.profile)
206 self.exit(1)
226 self.exit(1)
207 else:
227 else:
208 self.log.info("Using existing profile dir: %r"%p.location)
228 self.log.info("Using existing profile dir: %r"%p.location)
209 else:
229 else:
210 # location is fully specified
230 # location is fully specified
211 try:
231 try:
212 p = ProfileDir.find_profile_dir(location, self.config)
232 p = ProfileDir.find_profile_dir(location, self.config)
213 except ProfileDirError:
233 except ProfileDirError:
214 # not found, maybe create it
234 # not found, maybe create it
215 if self.auto_create:
235 if self.auto_create:
216 try:
236 try:
217 p = ProfileDir.create_profile_dir(location, self.config)
237 p = ProfileDir.create_profile_dir(location, self.config)
218 except ProfileDirError:
238 except ProfileDirError:
219 self.log.fatal("Could not create profile directory: %r"%location)
239 self.log.fatal("Could not create profile directory: %r"%location)
220 self.exit(1)
240 self.exit(1)
221 else:
241 else:
222 self.log.info("Creating new profile dir: %r"%location)
242 self.log.info("Creating new profile dir: %r"%location)
223 else:
243 else:
224 self.log.fatal("Profile directory %r not found."%location)
244 self.log.fatal("Profile directory %r not found."%location)
225 self.exit(1)
245 self.exit(1)
226 else:
246 else:
227 self.log.info("Using existing profile dir: %r"%location)
247 self.log.info("Using existing profile dir: %r"%location)
228
248
229 self.profile_dir = p
249 self.profile_dir = p
230 self.config_file_paths.append(p.location)
250 self.config_file_paths.append(p.location)
231
251
232 def init_config_files(self):
252 def init_config_files(self):
233 """[optionally] copy default config files into profile dir."""
253 """[optionally] copy default config files into profile dir."""
234 # copy config files
254 # copy config files
235 if self.copy_config_files:
255 if self.copy_config_files:
236 path = self.builtin_profile_dir
256 path = self.builtin_profile_dir
237 src = self.profile
257 src = self.profile
238 if not os.path.exists(path):
239 # use default if new profile doesn't have a preset
240 path = None
241 src = 'default'
242
258
243 self.log.debug("Staging %s config files into %r [overwrite=%s]"%(
259 cfg = self.config_file_name
244 src, self.profile_dir.location, self.overwrite)
260 if path and os.path.exists(os.path.join(path, cfg)):
261 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
262 cfg, src, self.profile_dir.location, self.overwrite)
245 )
263 )
246
247 for cfg in self.config_files:
248 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
264 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
265 else:
266 self.stage_default_config_file()
267
268 def stage_default_config_file(self):
269 """auto generate default config file, and stage it into the profile."""
270 s = self.generate_config_file()
271 fname = os.path.join(self.profile_dir.location, self.config_file_name)
272 if self.overwrite or not os.path.exists(fname):
273 self.log.warn("Generating default config file: %r"%(fname))
274 with open(fname, 'w') as f:
275 f.write(s)
276
249
277
250 def initialize(self, argv=None):
278 def initialize(self, argv=None):
251 self.init_crash_handler()
279 self.init_crash_handler()
252 self.parse_command_line(argv)
280 self.parse_command_line(argv)
281 if self.subapp is not None:
282 # stop here if subapp is taking over
283 return
253 cl_config = self.config
284 cl_config = self.config
254 self.init_profile_dir()
285 self.init_profile_dir()
255 self.init_config_files()
286 self.init_config_files()
256 self.load_config_file()
287 self.load_config_file()
257 # enforce cl-opts override configfile opts:
288 # enforce cl-opts override configfile opts:
258 self.update_config(cl_config)
289 self.update_config(cl_config)
259
290
@@ -1,174 +1,210 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for managing IPython profiles.
3 An application for managing IPython profiles.
4
4
5 To be invoked as the `ipython profile` subcommand.
5 To be invoked as the `ipython profile` subcommand.
6
6
7 Authors:
7 Authors:
8
8
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import logging
24 import logging
25 import os
25 import os
26
26
27 from IPython.config.application import Application, boolean_flag
27 from IPython.config.application import Application, boolean_flag
28 from IPython.core.application import (
28 from IPython.core.application import (
29 BaseIPythonApplication, base_flags, base_aliases
29 BaseIPythonApplication, base_flags, base_aliases
30 )
30 )
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import get_ipython_dir
32 from IPython.utils.path import get_ipython_dir
33 from IPython.utils.traitlets import Unicode, Bool, Dict
33 from IPython.utils.traitlets import Unicode, Bool, Dict
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Constants
36 # Constants
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 create_help = """Create an ipcluster profile by name
39 create_help = """Create an ipcluster profile by name
40
40
41 Create an ipython profile directory by its name or
41 Create an ipython profile directory by its name or
42 profile directory path. Profile directories contain
42 profile directory path. Profile directories contain
43 configuration, log and security related files and are named
43 configuration, log and security related files and are named
44 using the convention 'profile_<name>'. By default they are
44 using the convention 'profile_<name>'. By default they are
45 located in your ipython directory. Once created, you will
45 located in your ipython directory. Once created, you will
46 can edit the configuration files in the profile
46 can edit the configuration files in the profile
47 directory to configure IPython. Most users will create a
47 directory to configure IPython. Most users will create a
48 cluster directory by profile name,
48 cluster directory by profile name,
49 `ipython profile create myprofile`, which will put the directory
49 `ipython profile create myprofile`, which will put the directory
50 in `<ipython_dir>/profile_myprofile`.
50 in `<ipython_dir>/profile_myprofile`.
51 """
51 """
52 list_help = """List available IPython profiles
52 list_help = """List available IPython profiles
53
53
54 List all available profiles, by profile location, that can
54 List all available profiles, by profile location, that can
55 be found in the current working directly or in the ipython
55 be found in the current working directly or in the ipython
56 directory. Profile directories are named using the convention
56 directory. Profile directories are named using the convention
57 'profile_<profile>'.
57 'profile_<profile>'.
58 """
58 """
59 profile_help = """Manage IPython profiles
59 profile_help = """Manage IPython profiles
60
60
61 Profile directories contain
61 Profile directories contain
62 configuration, log and security related files and are named
62 configuration, log and security related files and are named
63 using the convention 'profile_<name>'. By default they are
63 using the convention 'profile_<name>'. By default they are
64 located in your ipython directory. You can create profiles
64 located in your ipython directory. You can create profiles
65 with `ipython profile create <name>`, or see the profiles you
65 with `ipython profile create <name>`, or see the profiles you
66 already have with `ipython profile list`
66 already have with `ipython profile list`
67
67
68 To get started configuring IPython, simply do:
68 To get started configuring IPython, simply do:
69
69
70 $> ipython profile create
70 $> ipython profile create
71
71
72 and IPython will create the default profile in <ipython_dir>/profile_default,
72 and IPython will create the default profile in <ipython_dir>/profile_default,
73 where you can edit ipython_config.py to start configuring IPython.
73 where you can edit ipython_config.py to start configuring IPython.
74
74
75 """
75 """
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Profile Application Class (for `ipython profile` subcommand)
78 # Profile Application Class (for `ipython profile` subcommand)
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81
81
82
82
83 class ProfileList(Application):
83 class ProfileList(Application):
84 name = u'ipython-profile'
84 name = u'ipython-profile'
85 description = list_help
85 description = list_help
86
86
87 aliases = Dict(dict(
87 aliases = Dict(dict(
88 ipython_dir = 'ProfileList.ipython_dir',
88 ipython_dir = 'ProfileList.ipython_dir',
89 log_level = 'Application.log_level',
89 log_level = 'Application.log_level',
90 ))
90 ))
91 flags = Dict(dict(
91 flags = Dict(dict(
92 debug = ({'Application' : {'log_level' : 0}},
92 debug = ({'Application' : {'log_level' : 0}},
93 "Set log_level to 0, maximizing log output."
93 "Set log_level to 0, maximizing log output."
94 )
94 )
95 ))
95 ))
96 ipython_dir = Unicode(get_ipython_dir(), config=True,
96 ipython_dir = Unicode(get_ipython_dir(), config=True,
97 help="""
97 help="""
98 The name of the IPython directory. This directory is used for logging
98 The name of the IPython directory. This directory is used for logging
99 configuration (through profiles), history storage, etc. The default
99 configuration (through profiles), history storage, etc. The default
100 is usually $HOME/.ipython. This options can also be specified through
100 is usually $HOME/.ipython. This options can also be specified through
101 the environment variable IPYTHON_DIR.
101 the environment variable IPYTHON_DIR.
102 """
102 """
103 )
103 )
104
104
105 def list_profile_dirs(self):
105 def list_profile_dirs(self):
106 # Find the search paths
106 # Find the search paths
107 paths = [os.getcwdu(), self.ipython_dir]
107 paths = [os.getcwdu(), self.ipython_dir]
108
108
109 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
109 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
110 for path in paths:
110 for path in paths:
111 files = os.listdir(path)
111 files = os.listdir(path)
112 for f in files:
112 for f in files:
113 full_path = os.path.join(path, f)
113 full_path = os.path.join(path, f)
114 if os.path.isdir(full_path) and f.startswith('profile_'):
114 if os.path.isdir(full_path) and f.startswith('profile_'):
115 profile = f.split('_',1)[-1]
115 profile = f.split('_',1)[-1]
116 start_cmd = 'ipython profile=%s' % profile
116 start_cmd = 'ipython profile=%s' % profile
117 print start_cmd + " ==> " + full_path
117 print start_cmd + " ==> " + full_path
118
118
119 def start(self):
119 def start(self):
120 self.list_profile_dirs()
120 self.list_profile_dirs()
121
121
122
122
123 create_flags = {}
123 create_flags = {}
124 create_flags.update(base_flags)
124 create_flags.update(base_flags)
125 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
125 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
126 "reset config files to defaults", "leave existing config files"))
126 "reset config files to defaults", "leave existing config files"))
127 create_flags.update(boolean_flag('cluster', 'ProfileCreate.cluster',
127 create_flags.update(boolean_flag('cluster', 'ProfileCreate.cluster',
128 "Include parallel computing config files",
128 "Include parallel computing config files",
129 "Don't include parallel computing config files"))
129 "Don't include parallel computing config files"))
130
130
131 class ProfileCreate(BaseIPythonApplication):
131 class ProfileCreate(BaseIPythonApplication):
132 name = u'ipython-profile'
132 name = u'ipython-profile'
133 description = create_help
133 description = create_help
134 auto_create = Bool(True, config=False)
134 auto_create = Bool(True, config=False)
135
135
136 def _copy_config_files_default(self):
136 def _copy_config_files_default(self):
137 return True
137 return True
138
138
139 cluster = Bool(False, config=True,
139 cluster = Bool(False, config=True,
140 help="whether to include parallel computing config files")
140 help="whether to include parallel computing config files")
141 def _cluster_changed(self, name, old, new):
141 def _cluster_changed(self, name, old, new):
142 cluster_files = [ 'ipcontroller_config.py',
142 cluster_files = [ 'ipcontroller_config.py',
143 'ipengine_config.py',
143 'ipengine_config.py',
144 'ipcluster_config.py'
144 'ipcluster_config.py'
145 ]
145 ]
146 if new:
146 if new:
147 for cf in cluster_files:
147 for cf in cluster_files:
148 self.config_files.append(cf)
148 self.config_files.append(cf)
149 else:
149 else:
150 for cf in cluster_files:
150 for cf in cluster_files:
151 if cf in self.config_files:
151 if cf in self.config_files:
152 self.config_files.remove(cf)
152 self.config_files.remove(cf)
153
153
154 def parse_command_line(self, argv):
154 def parse_command_line(self, argv):
155 super(ProfileCreate, self).parse_command_line(argv)
155 super(ProfileCreate, self).parse_command_line(argv)
156 # accept positional arg as profile name
156 # accept positional arg as profile name
157 if self.extra_args:
157 if self.extra_args:
158 self.profile = self.extra_args[0]
158 self.profile = self.extra_args[0]
159
159
160 flags = Dict(create_flags)
160 flags = Dict(create_flags)
161
161
162 aliases = Dict(dict(profile='BaseIPythonApplication.profile'))
162 aliases = Dict(dict(profile='BaseIPythonApplication.profile'))
163
163
164 classes = [ProfileDir]
164 classes = [ProfileDir]
165
165
166 def init_config_files(self):
167 super(ProfileCreate, self).init_config_files()
168 # use local imports, since these classes may import from here
169 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
170 apps = [TerminalIPythonApp]
171 try:
172 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
173 except ImportError:
174 pass
175 else:
176 apps.append(IPythonQtConsoleApp)
177 if self.cluster:
178 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
179 from IPython.parallel.apps.ipengineapp import IPEngineApp
180 from IPython.parallel.apps.ipclusterapp import IPClusterStart
181 from IPython.parallel.apps.iploggerapp import IPLoggerApp
182 apps.extend([
183 IPControllerApp,
184 IPEngineApp,
185 IPClusterStart,
186 IPLoggerApp,
187 ])
188 for App in apps:
189 app = App()
190 app.config.update(self.config)
191 app.log = self.log
192 app.overwrite = self.overwrite
193 app.copy_config_files=True
194 app.profile = self.profile
195 app.init_profile_dir()
196 app.init_config_files()
197 print 'tic'
198
199 def stage_default_config_file(self):
200 pass
201
166 class ProfileApp(Application):
202 class ProfileApp(Application):
167 name = u'ipython-profile'
203 name = u'ipython-profile'
168 description = profile_help
204 description = profile_help
169
205
170 subcommands = Dict(dict(
206 subcommands = Dict(dict(
171 create = (ProfileCreate, "Create a new profile dir with default config files"),
207 create = (ProfileCreate, "Create a new profile dir with default config files"),
172 list = (ProfileList, "List existing profiles")
208 list = (ProfileList, "List existing profiles")
173 ))
209 ))
174
210
@@ -1,358 +1,357 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2010 The IPython Development Team
16 # Copyright (C) 2008-2010 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader
33 Config, PyFileConfigLoader
34 )
34 )
35 from IPython.config.application import boolean_flag
35 from IPython.config.application import boolean_flag
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.crashhandler import CrashHandler
38 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.formatters import PlainTextFormatter
39 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.application import (
40 from IPython.core.application import (
41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 )
42 )
43 from IPython.core.shellapp import (
43 from IPython.core.shellapp import (
44 InteractiveShellApp, shell_flags, shell_aliases
44 InteractiveShellApp, shell_flags, shell_aliases
45 )
45 )
46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 from IPython.lib import inputhook
47 from IPython.lib import inputhook
48 from IPython.utils.path import get_ipython_dir, check_for_old_config
48 from IPython.utils.path import get_ipython_dir, check_for_old_config
49 from IPython.utils.traitlets import (
49 from IPython.utils.traitlets import (
50 Bool, Dict, CaselessStrEnum
50 Bool, Dict, CaselessStrEnum
51 )
51 )
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Globals, utilities and helpers
54 # Globals, utilities and helpers
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 #: The default config file name for this application.
57 #: The default config file name for this application.
58 default_config_file_name = u'ipython_config.py'
58 default_config_file_name = u'ipython_config.py'
59
59
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Crash handler for this application
62 # Crash handler for this application
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 _message_template = """\
65 _message_template = """\
66 Oops, $self.app_name crashed. We do our best to make it stable, but...
66 Oops, $self.app_name crashed. We do our best to make it stable, but...
67
67
68 A crash report was automatically generated with the following information:
68 A crash report was automatically generated with the following information:
69 - A verbatim copy of the crash traceback.
69 - A verbatim copy of the crash traceback.
70 - A copy of your input history during this session.
70 - A copy of your input history during this session.
71 - Data on your current $self.app_name configuration.
71 - Data on your current $self.app_name configuration.
72
72
73 It was left in the file named:
73 It was left in the file named:
74 \t'$self.crash_report_fname'
74 \t'$self.crash_report_fname'
75 If you can email this file to the developers, the information in it will help
75 If you can email this file to the developers, the information in it will help
76 them in understanding and correcting the problem.
76 them in understanding and correcting the problem.
77
77
78 You can mail it to: $self.contact_name at $self.contact_email
78 You can mail it to: $self.contact_name at $self.contact_email
79 with the subject '$self.app_name Crash Report'.
79 with the subject '$self.app_name Crash Report'.
80
80
81 If you want to do it now, the following command will work (under Unix):
81 If you want to do it now, the following command will work (under Unix):
82 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
82 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
83
83
84 To ensure accurate tracking of this issue, please file a report about it at:
84 To ensure accurate tracking of this issue, please file a report about it at:
85 $self.bug_tracker
85 $self.bug_tracker
86 """
86 """
87
87
88 class IPAppCrashHandler(CrashHandler):
88 class IPAppCrashHandler(CrashHandler):
89 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
89 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
90
90
91 message_template = _message_template
91 message_template = _message_template
92
92
93 def __init__(self, app):
93 def __init__(self, app):
94 contact_name = release.authors['Fernando'][0]
94 contact_name = release.authors['Fernando'][0]
95 contact_email = release.authors['Fernando'][1]
95 contact_email = release.authors['Fernando'][1]
96 bug_tracker = 'http://github.com/ipython/ipython/issues'
96 bug_tracker = 'http://github.com/ipython/ipython/issues'
97 super(IPAppCrashHandler,self).__init__(
97 super(IPAppCrashHandler,self).__init__(
98 app, contact_name, contact_email, bug_tracker
98 app, contact_name, contact_email, bug_tracker
99 )
99 )
100
100
101 def make_report(self,traceback):
101 def make_report(self,traceback):
102 """Return a string containing a crash report."""
102 """Return a string containing a crash report."""
103
103
104 sec_sep = self.section_sep
104 sec_sep = self.section_sep
105 # Start with parent report
105 # Start with parent report
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 # Add interactive-specific info we may have
107 # Add interactive-specific info we may have
108 rpt_add = report.append
108 rpt_add = report.append
109 try:
109 try:
110 rpt_add(sec_sep+"History of session input:")
110 rpt_add(sec_sep+"History of session input:")
111 for line in self.app.shell.user_ns['_ih']:
111 for line in self.app.shell.user_ns['_ih']:
112 rpt_add(line)
112 rpt_add(line)
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
115 except:
115 except:
116 pass
116 pass
117
117
118 return ''.join(report)
118 return ''.join(report)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Aliases and Flags
121 # Aliases and Flags
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123 flags = dict(base_flags)
123 flags = dict(base_flags)
124 flags.update(shell_flags)
124 flags.update(shell_flags)
125 addflag = lambda *args: flags.update(boolean_flag(*args))
125 addflag = lambda *args: flags.update(boolean_flag(*args))
126 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
126 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 'Turn on auto editing of files with syntax errors.',
127 'Turn on auto editing of files with syntax errors.',
128 'Turn off auto editing of files with syntax errors.'
128 'Turn off auto editing of files with syntax errors.'
129 )
129 )
130 addflag('banner', 'TerminalIPythonApp.display_banner',
130 addflag('banner', 'TerminalIPythonApp.display_banner',
131 "Display a banner upon starting IPython.",
131 "Display a banner upon starting IPython.",
132 "Don't display a banner upon starting IPython."
132 "Don't display a banner upon starting IPython."
133 )
133 )
134 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
134 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 """Set to confirm when you try to exit IPython with an EOF (Control-D
135 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
136 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 you can force a direct exit without any confirmation.""",
137 you can force a direct exit without any confirmation.""",
138 "Don't prompt the user when exiting."
138 "Don't prompt the user when exiting."
139 )
139 )
140 addflag('term-title', 'TerminalInteractiveShell.term_title',
140 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 "Enable auto setting the terminal title.",
141 "Enable auto setting the terminal title.",
142 "Disable auto setting the terminal title."
142 "Disable auto setting the terminal title."
143 )
143 )
144 classic_config = Config()
144 classic_config = Config()
145 classic_config.InteractiveShell.cache_size = 0
145 classic_config.InteractiveShell.cache_size = 0
146 classic_config.PlainTextFormatter.pprint = False
146 classic_config.PlainTextFormatter.pprint = False
147 classic_config.InteractiveShell.prompt_in1 = '>>> '
147 classic_config.InteractiveShell.prompt_in1 = '>>> '
148 classic_config.InteractiveShell.prompt_in2 = '... '
148 classic_config.InteractiveShell.prompt_in2 = '... '
149 classic_config.InteractiveShell.prompt_out = ''
149 classic_config.InteractiveShell.prompt_out = ''
150 classic_config.InteractiveShell.separate_in = ''
150 classic_config.InteractiveShell.separate_in = ''
151 classic_config.InteractiveShell.separate_out = ''
151 classic_config.InteractiveShell.separate_out = ''
152 classic_config.InteractiveShell.separate_out2 = ''
152 classic_config.InteractiveShell.separate_out2 = ''
153 classic_config.InteractiveShell.colors = 'NoColor'
153 classic_config.InteractiveShell.colors = 'NoColor'
154 classic_config.InteractiveShell.xmode = 'Plain'
154 classic_config.InteractiveShell.xmode = 'Plain'
155
155
156 flags['classic']=(
156 flags['classic']=(
157 classic_config,
157 classic_config,
158 "Gives IPython a similar feel to the classic Python prompt."
158 "Gives IPython a similar feel to the classic Python prompt."
159 )
159 )
160 # # log doesn't make so much sense this way anymore
160 # # log doesn't make so much sense this way anymore
161 # paa('--log','-l',
161 # paa('--log','-l',
162 # action='store_true', dest='InteractiveShell.logstart',
162 # action='store_true', dest='InteractiveShell.logstart',
163 # help="Start logging to the default log file (./ipython_log.py).")
163 # help="Start logging to the default log file (./ipython_log.py).")
164 #
164 #
165 # # quick is harder to implement
165 # # quick is harder to implement
166 flags['quick']=(
166 flags['quick']=(
167 {'TerminalIPythonApp' : {'quick' : True}},
167 {'TerminalIPythonApp' : {'quick' : True}},
168 "Enable quick startup with no config files."
168 "Enable quick startup with no config files."
169 )
169 )
170
170
171 flags['i'] = (
171 flags['i'] = (
172 {'TerminalIPythonApp' : {'force_interact' : True}},
172 {'TerminalIPythonApp' : {'force_interact' : True}},
173 "If running code from the command line, become interactive afterwards."
173 "If running code from the command line, become interactive afterwards."
174 )
174 )
175 flags['pylab'] = (
175 flags['pylab'] = (
176 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
176 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
177 """Pre-load matplotlib and numpy for interactive use with
177 """Pre-load matplotlib and numpy for interactive use with
178 the default matplotlib backend."""
178 the default matplotlib backend."""
179 )
179 )
180
180
181 aliases = dict(base_aliases)
181 aliases = dict(base_aliases)
182 aliases.update(shell_aliases)
182 aliases.update(shell_aliases)
183
183
184 # it's possible we don't want short aliases for *all* of these:
184 # it's possible we don't want short aliases for *all* of these:
185 aliases.update(dict(
185 aliases.update(dict(
186 gui='TerminalIPythonApp.gui',
186 gui='TerminalIPythonApp.gui',
187 pylab='TerminalIPythonApp.pylab',
187 pylab='TerminalIPythonApp.pylab',
188 ))
188 ))
189
189
190 #-----------------------------------------------------------------------------
190 #-----------------------------------------------------------------------------
191 # Main classes and functions
191 # Main classes and functions
192 #-----------------------------------------------------------------------------
192 #-----------------------------------------------------------------------------
193
193
194 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
194 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
195 name = u'ipython'
195 name = u'ipython'
196 description = usage.cl_usage
196 description = usage.cl_usage
197 default_config_file_name = default_config_file_name
197 default_config_file_name = default_config_file_name
198 crash_handler_class = IPAppCrashHandler
198 crash_handler_class = IPAppCrashHandler
199
199
200 flags = Dict(flags)
200 flags = Dict(flags)
201 aliases = Dict(aliases)
201 aliases = Dict(aliases)
202 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
202 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
203 subcommands = Dict(dict(
203 subcommands = Dict(dict(
204 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
204 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
205 """Launch the IPython Qt Console."""
205 """Launch the IPython Qt Console."""
206 ),
206 ),
207 profile = ("IPython.core.profileapp.ProfileApp",
207 profile = ("IPython.core.profileapp.ProfileApp",
208 "Create and manage IPython profiles.")
208 "Create and manage IPython profiles.")
209 ))
209 ))
210
210
211 # *do* autocreate requested profile
211 # *do* autocreate requested profile, but don't create the config file.
212 auto_create=Bool(True)
212 auto_create=Bool(True)
213 copy_config_files=Bool(True)
214 # configurables
213 # configurables
215 ignore_old_config=Bool(False, config=True,
214 ignore_old_config=Bool(False, config=True,
216 help="Suppress warning messages about legacy config files"
215 help="Suppress warning messages about legacy config files"
217 )
216 )
218 quick = Bool(False, config=True,
217 quick = Bool(False, config=True,
219 help="""Start IPython quickly by skipping the loading of config files."""
218 help="""Start IPython quickly by skipping the loading of config files."""
220 )
219 )
221 def _quick_changed(self, name, old, new):
220 def _quick_changed(self, name, old, new):
222 if new:
221 if new:
223 self.load_config_file = lambda *a, **kw: None
222 self.load_config_file = lambda *a, **kw: None
224 self.ignore_old_config=True
223 self.ignore_old_config=True
225
224
226 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
225 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
227 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
226 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
228 )
227 )
229 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
228 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
230 config=True,
229 config=True,
231 help="""Pre-load matplotlib and numpy for interactive use,
230 help="""Pre-load matplotlib and numpy for interactive use,
232 selecting a particular matplotlib backend and loop integration.
231 selecting a particular matplotlib backend and loop integration.
233 """
232 """
234 )
233 )
235 display_banner = Bool(True, config=True,
234 display_banner = Bool(True, config=True,
236 help="Whether to display a banner upon starting IPython."
235 help="Whether to display a banner upon starting IPython."
237 )
236 )
238
237
239 # if there is code of files to run from the cmd line, don't interact
238 # if there is code of files to run from the cmd line, don't interact
240 # unless the --i flag (App.force_interact) is true.
239 # unless the --i flag (App.force_interact) is true.
241 force_interact = Bool(False, config=True,
240 force_interact = Bool(False, config=True,
242 help="""If a command or file is given via the command-line,
241 help="""If a command or file is given via the command-line,
243 e.g. 'ipython foo.py"""
242 e.g. 'ipython foo.py"""
244 )
243 )
245 def _force_interact_changed(self, name, old, new):
244 def _force_interact_changed(self, name, old, new):
246 if new:
245 if new:
247 self.interact = True
246 self.interact = True
248
247
249 def _file_to_run_changed(self, name, old, new):
248 def _file_to_run_changed(self, name, old, new):
250 if new and not self.force_interact:
249 if new and not self.force_interact:
251 self.interact = False
250 self.interact = False
252 _code_to_run_changed = _file_to_run_changed
251 _code_to_run_changed = _file_to_run_changed
253
252
254 # internal, not-configurable
253 # internal, not-configurable
255 interact=Bool(True)
254 interact=Bool(True)
256
255
257
256
258 def initialize(self, argv=None):
257 def initialize(self, argv=None):
259 """Do actions after construct, but before starting the app."""
258 """Do actions after construct, but before starting the app."""
260 super(TerminalIPythonApp, self).initialize(argv)
259 super(TerminalIPythonApp, self).initialize(argv)
261 if self.subapp is not None:
260 if self.subapp is not None:
262 # don't bother initializing further, starting subapp
261 # don't bother initializing further, starting subapp
263 return
262 return
264 if not self.ignore_old_config:
263 if not self.ignore_old_config:
265 check_for_old_config(self.ipython_dir)
264 check_for_old_config(self.ipython_dir)
266 # print self.extra_args
265 # print self.extra_args
267 if self.extra_args:
266 if self.extra_args:
268 self.file_to_run = self.extra_args[0]
267 self.file_to_run = self.extra_args[0]
269 # create the shell
268 # create the shell
270 self.init_shell()
269 self.init_shell()
271 # and draw the banner
270 # and draw the banner
272 self.init_banner()
271 self.init_banner()
273 # Now a variety of things that happen after the banner is printed.
272 # Now a variety of things that happen after the banner is printed.
274 self.init_gui_pylab()
273 self.init_gui_pylab()
275 self.init_extensions()
274 self.init_extensions()
276 self.init_code()
275 self.init_code()
277
276
278 def init_shell(self):
277 def init_shell(self):
279 """initialize the InteractiveShell instance"""
278 """initialize the InteractiveShell instance"""
280 # I am a little hesitant to put these into InteractiveShell itself.
279 # I am a little hesitant to put these into InteractiveShell itself.
281 # But that might be the place for them
280 # But that might be the place for them
282 sys.path.insert(0, '')
281 sys.path.insert(0, '')
283
282
284 # Create an InteractiveShell instance.
283 # Create an InteractiveShell instance.
285 # shell.display_banner should always be False for the terminal
284 # shell.display_banner should always be False for the terminal
286 # based app, because we call shell.show_banner() by hand below
285 # based app, because we call shell.show_banner() by hand below
287 # so the banner shows *before* all extension loading stuff.
286 # so the banner shows *before* all extension loading stuff.
288 self.shell = TerminalInteractiveShell.instance(config=self.config,
287 self.shell = TerminalInteractiveShell.instance(config=self.config,
289 display_banner=False, profile_dir=self.profile_dir,
288 display_banner=False, profile_dir=self.profile_dir,
290 ipython_dir=self.ipython_dir)
289 ipython_dir=self.ipython_dir)
291
290
292 def init_banner(self):
291 def init_banner(self):
293 """optionally display the banner"""
292 """optionally display the banner"""
294 if self.display_banner and self.interact:
293 if self.display_banner and self.interact:
295 self.shell.show_banner()
294 self.shell.show_banner()
296 # Make sure there is a space below the banner.
295 # Make sure there is a space below the banner.
297 if self.log_level <= logging.INFO: print
296 if self.log_level <= logging.INFO: print
298
297
299
298
300 def init_gui_pylab(self):
299 def init_gui_pylab(self):
301 """Enable GUI event loop integration, taking pylab into account."""
300 """Enable GUI event loop integration, taking pylab into account."""
302 gui = self.gui
301 gui = self.gui
303
302
304 # Using `pylab` will also require gui activation, though which toolkit
303 # Using `pylab` will also require gui activation, though which toolkit
305 # to use may be chosen automatically based on mpl configuration.
304 # to use may be chosen automatically based on mpl configuration.
306 if self.pylab:
305 if self.pylab:
307 activate = self.shell.enable_pylab
306 activate = self.shell.enable_pylab
308 if self.pylab == 'auto':
307 if self.pylab == 'auto':
309 gui = None
308 gui = None
310 else:
309 else:
311 gui = self.pylab
310 gui = self.pylab
312 else:
311 else:
313 # Enable only GUI integration, no pylab
312 # Enable only GUI integration, no pylab
314 activate = inputhook.enable_gui
313 activate = inputhook.enable_gui
315
314
316 if gui or self.pylab:
315 if gui or self.pylab:
317 try:
316 try:
318 self.log.info("Enabling GUI event loop integration, "
317 self.log.info("Enabling GUI event loop integration, "
319 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
318 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
320 activate(gui)
319 activate(gui)
321 except:
320 except:
322 self.log.warn("Error in enabling GUI event loop integration:")
321 self.log.warn("Error in enabling GUI event loop integration:")
323 self.shell.showtraceback()
322 self.shell.showtraceback()
324
323
325 def start(self):
324 def start(self):
326 if self.subapp is not None:
325 if self.subapp is not None:
327 return self.subapp.start()
326 return self.subapp.start()
328 # perform any prexec steps:
327 # perform any prexec steps:
329 if self.interact:
328 if self.interact:
330 self.log.debug("Starting IPython's mainloop...")
329 self.log.debug("Starting IPython's mainloop...")
331 self.shell.mainloop()
330 self.shell.mainloop()
332 else:
331 else:
333 self.log.debug("IPython not interactive...")
332 self.log.debug("IPython not interactive...")
334
333
335
334
336 def load_default_config(ipython_dir=None):
335 def load_default_config(ipython_dir=None):
337 """Load the default config file from the default ipython_dir.
336 """Load the default config file from the default ipython_dir.
338
337
339 This is useful for embedded shells.
338 This is useful for embedded shells.
340 """
339 """
341 if ipython_dir is None:
340 if ipython_dir is None:
342 ipython_dir = get_ipython_dir()
341 ipython_dir = get_ipython_dir()
343 profile_dir = os.path.join(ipython_dir, 'profile_default')
342 profile_dir = os.path.join(ipython_dir, 'profile_default')
344 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
343 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
345 config = cl.load_config()
344 config = cl.load_config()
346 return config
345 return config
347
346
348
347
349 def launch_new_instance():
348 def launch_new_instance():
350 """Create and run a full blown IPython instance"""
349 """Create and run a full blown IPython instance"""
351 app = TerminalIPythonApp.instance()
350 app = TerminalIPythonApp.instance()
352 app.initialize()
351 app.initialize()
353 app.start()
352 app.start()
354
353
355
354
356 if __name__ == '__main__':
355 if __name__ == '__main__':
357 launch_new_instance()
356 launch_new_instance()
358
357
@@ -1,446 +1,445 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The ipcluster application.
4 The ipcluster application.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * MinRK
9 * MinRK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import errno
24 import errno
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import signal
28 import signal
29
29
30 from subprocess import check_call, CalledProcessError, PIPE
30 from subprocess import check_call, CalledProcessError, PIPE
31 import zmq
31 import zmq
32 from zmq.eventloop import ioloop
32 from zmq.eventloop import ioloop
33
33
34 from IPython.config.application import Application, boolean_flag
34 from IPython.config.application import Application, boolean_flag
35 from IPython.config.loader import Config
35 from IPython.config.loader import Config
36 from IPython.core.application import BaseIPythonApplication
36 from IPython.core.application import BaseIPythonApplication
37 from IPython.core.profiledir import ProfileDir
37 from IPython.core.profiledir import ProfileDir
38 from IPython.utils.daemonize import daemonize
38 from IPython.utils.daemonize import daemonize
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.utils.traitlets import Int, Unicode, Bool, CFloat, Dict, List
40 from IPython.utils.traitlets import Int, Unicode, Bool, CFloat, Dict, List
41
41
42 from IPython.parallel.apps.baseapp import (
42 from IPython.parallel.apps.baseapp import (
43 BaseParallelApplication,
43 BaseParallelApplication,
44 PIDFileError,
44 PIDFileError,
45 base_flags, base_aliases
45 base_flags, base_aliases
46 )
46 )
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Module level variables
50 # Module level variables
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53
53
54 default_config_file_name = u'ipcluster_config.py'
54 default_config_file_name = u'ipcluster_config.py'
55
55
56
56
57 _description = """Start an IPython cluster for parallel computing.
57 _description = """Start an IPython cluster for parallel computing.
58
58
59 An IPython cluster consists of 1 controller and 1 or more engines.
59 An IPython cluster consists of 1 controller and 1 or more engines.
60 This command automates the startup of these processes using a wide
60 This command automates the startup of these processes using a wide
61 range of startup methods (SSH, local processes, PBS, mpiexec,
61 range of startup methods (SSH, local processes, PBS, mpiexec,
62 Windows HPC Server 2008). To start a cluster with 4 engines on your
62 Windows HPC Server 2008). To start a cluster with 4 engines on your
63 local host simply do 'ipcluster start n=4'. For more complex usage
63 local host simply do 'ipcluster start n=4'. For more complex usage
64 you will typically do 'ipcluster create profile=mycluster', then edit
64 you will typically do 'ipcluster create profile=mycluster', then edit
65 configuration files, followed by 'ipcluster start profile=mycluster n=4'.
65 configuration files, followed by 'ipcluster start profile=mycluster n=4'.
66 """
66 """
67
67
68
68
69 # Exit codes for ipcluster
69 # Exit codes for ipcluster
70
70
71 # This will be the exit code if the ipcluster appears to be running because
71 # This will be the exit code if the ipcluster appears to be running because
72 # a .pid file exists
72 # a .pid file exists
73 ALREADY_STARTED = 10
73 ALREADY_STARTED = 10
74
74
75
75
76 # This will be the exit code if ipcluster stop is run, but there is not .pid
76 # This will be the exit code if ipcluster stop is run, but there is not .pid
77 # file to be found.
77 # file to be found.
78 ALREADY_STOPPED = 11
78 ALREADY_STOPPED = 11
79
79
80 # This will be the exit code if ipcluster engines is run, but there is not .pid
80 # This will be the exit code if ipcluster engines is run, but there is not .pid
81 # file to be found.
81 # file to be found.
82 NO_CLUSTER = 12
82 NO_CLUSTER = 12
83
83
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # Main application
86 # Main application
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 start_help = """Start an IPython cluster for parallel computing
88 start_help = """Start an IPython cluster for parallel computing
89
89
90 Start an ipython cluster by its profile name or cluster
90 Start an ipython cluster by its profile name or cluster
91 directory. Cluster directories contain configuration, log and
91 directory. Cluster directories contain configuration, log and
92 security related files and are named using the convention
92 security related files and are named using the convention
93 'profile_<name>' and should be creating using the 'start'
93 'profile_<name>' and should be creating using the 'start'
94 subcommand of 'ipcluster'. If your cluster directory is in
94 subcommand of 'ipcluster'. If your cluster directory is in
95 the cwd or the ipython directory, you can simply refer to it
95 the cwd or the ipython directory, you can simply refer to it
96 using its profile name, 'ipcluster start n=4 profile=<profile>`,
96 using its profile name, 'ipcluster start n=4 profile=<profile>`,
97 otherwise use the 'profile_dir' option.
97 otherwise use the 'profile_dir' option.
98 """
98 """
99 stop_help = """Stop a running IPython cluster
99 stop_help = """Stop a running IPython cluster
100
100
101 Stop a running ipython cluster by its profile name or cluster
101 Stop a running ipython cluster by its profile name or cluster
102 directory. Cluster directories are named using the convention
102 directory. Cluster directories are named using the convention
103 'profile_<name>'. If your cluster directory is in
103 'profile_<name>'. If your cluster directory is in
104 the cwd or the ipython directory, you can simply refer to it
104 the cwd or the ipython directory, you can simply refer to it
105 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
105 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
106 use the 'profile_dir' option.
106 use the 'profile_dir' option.
107 """
107 """
108 engines_help = """Start engines connected to an existing IPython cluster
108 engines_help = """Start engines connected to an existing IPython cluster
109
109
110 Start one or more engines to connect to an existing Cluster
110 Start one or more engines to connect to an existing Cluster
111 by profile name or cluster directory.
111 by profile name or cluster directory.
112 Cluster directories contain configuration, log and
112 Cluster directories contain configuration, log and
113 security related files and are named using the convention
113 security related files and are named using the convention
114 'profile_<name>' and should be creating using the 'start'
114 'profile_<name>' and should be creating using the 'start'
115 subcommand of 'ipcluster'. If your cluster directory is in
115 subcommand of 'ipcluster'. If your cluster directory is in
116 the cwd or the ipython directory, you can simply refer to it
116 the cwd or the ipython directory, you can simply refer to it
117 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
117 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
118 otherwise use the 'profile_dir' option.
118 otherwise use the 'profile_dir' option.
119 """
119 """
120 stop_aliases = dict(
120 stop_aliases = dict(
121 signal='IPClusterStop.signal',
121 signal='IPClusterStop.signal',
122 profile='BaseIPythonApplication.profile',
122 profile='BaseIPythonApplication.profile',
123 profile_dir='ProfileDir.location',
123 profile_dir='ProfileDir.location',
124 )
124 )
125
125
126 class IPClusterStop(BaseParallelApplication):
126 class IPClusterStop(BaseParallelApplication):
127 name = u'ipcluster'
127 name = u'ipcluster'
128 description = stop_help
128 description = stop_help
129 config_file_name = Unicode(default_config_file_name)
129 config_file_name = Unicode(default_config_file_name)
130
130
131 signal = Int(signal.SIGINT, config=True,
131 signal = Int(signal.SIGINT, config=True,
132 help="signal to use for stopping processes.")
132 help="signal to use for stopping processes.")
133
133
134 aliases = Dict(stop_aliases)
134 aliases = Dict(stop_aliases)
135
135
136 def start(self):
136 def start(self):
137 """Start the app for the stop subcommand."""
137 """Start the app for the stop subcommand."""
138 try:
138 try:
139 pid = self.get_pid_from_file()
139 pid = self.get_pid_from_file()
140 except PIDFileError:
140 except PIDFileError:
141 self.log.critical(
141 self.log.critical(
142 'Could not read pid file, cluster is probably not running.'
142 'Could not read pid file, cluster is probably not running.'
143 )
143 )
144 # Here I exit with a unusual exit status that other processes
144 # Here I exit with a unusual exit status that other processes
145 # can watch for to learn how I existed.
145 # can watch for to learn how I existed.
146 self.remove_pid_file()
146 self.remove_pid_file()
147 self.exit(ALREADY_STOPPED)
147 self.exit(ALREADY_STOPPED)
148
148
149 if not self.check_pid(pid):
149 if not self.check_pid(pid):
150 self.log.critical(
150 self.log.critical(
151 'Cluster [pid=%r] is not running.' % pid
151 'Cluster [pid=%r] is not running.' % pid
152 )
152 )
153 self.remove_pid_file()
153 self.remove_pid_file()
154 # Here I exit with a unusual exit status that other processes
154 # Here I exit with a unusual exit status that other processes
155 # can watch for to learn how I existed.
155 # can watch for to learn how I existed.
156 self.exit(ALREADY_STOPPED)
156 self.exit(ALREADY_STOPPED)
157
157
158 elif os.name=='posix':
158 elif os.name=='posix':
159 sig = self.signal
159 sig = self.signal
160 self.log.info(
160 self.log.info(
161 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
161 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
162 )
162 )
163 try:
163 try:
164 os.kill(pid, sig)
164 os.kill(pid, sig)
165 except OSError:
165 except OSError:
166 self.log.error("Stopping cluster failed, assuming already dead.",
166 self.log.error("Stopping cluster failed, assuming already dead.",
167 exc_info=True)
167 exc_info=True)
168 self.remove_pid_file()
168 self.remove_pid_file()
169 elif os.name=='nt':
169 elif os.name=='nt':
170 try:
170 try:
171 # kill the whole tree
171 # kill the whole tree
172 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
172 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
173 except (CalledProcessError, OSError):
173 except (CalledProcessError, OSError):
174 self.log.error("Stopping cluster failed, assuming already dead.",
174 self.log.error("Stopping cluster failed, assuming already dead.",
175 exc_info=True)
175 exc_info=True)
176 self.remove_pid_file()
176 self.remove_pid_file()
177
177
178 engine_aliases = {}
178 engine_aliases = {}
179 engine_aliases.update(base_aliases)
179 engine_aliases.update(base_aliases)
180 engine_aliases.update(dict(
180 engine_aliases.update(dict(
181 n='IPClusterEngines.n',
181 n='IPClusterEngines.n',
182 elauncher = 'IPClusterEngines.engine_launcher_class',
182 elauncher = 'IPClusterEngines.engine_launcher_class',
183 ))
183 ))
184 class IPClusterEngines(BaseParallelApplication):
184 class IPClusterEngines(BaseParallelApplication):
185
185
186 name = u'ipcluster'
186 name = u'ipcluster'
187 description = engines_help
187 description = engines_help
188 usage = None
188 usage = None
189 config_file_name = Unicode(default_config_file_name)
189 config_file_name = Unicode(default_config_file_name)
190 default_log_level = logging.INFO
190 default_log_level = logging.INFO
191 classes = List()
191 classes = List()
192 def _classes_default(self):
192 def _classes_default(self):
193 from IPython.parallel.apps import launcher
193 from IPython.parallel.apps import launcher
194 launchers = launcher.all_launchers
194 launchers = launcher.all_launchers
195 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
195 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
196 return [ProfileDir]+eslaunchers
196 return [ProfileDir]+eslaunchers
197
197
198 n = Int(2, config=True,
198 n = Int(2, config=True,
199 help="The number of engines to start.")
199 help="The number of engines to start.")
200
200
201 engine_launcher_class = Unicode('LocalEngineSetLauncher',
201 engine_launcher_class = Unicode('LocalEngineSetLauncher',
202 config=True,
202 config=True,
203 help="The class for launching a set of Engines."
203 help="The class for launching a set of Engines."
204 )
204 )
205 daemonize = Bool(False, config=True,
205 daemonize = Bool(False, config=True,
206 help='Daemonize the ipcluster program. This implies --log-to-file')
206 help='Daemonize the ipcluster program. This implies --log-to-file')
207
207
208 def _daemonize_changed(self, name, old, new):
208 def _daemonize_changed(self, name, old, new):
209 if new:
209 if new:
210 self.log_to_file = True
210 self.log_to_file = True
211
211
212 aliases = Dict(engine_aliases)
212 aliases = Dict(engine_aliases)
213 # flags = Dict(flags)
213 # flags = Dict(flags)
214 _stopping = False
214 _stopping = False
215
215
216 def initialize(self, argv=None):
216 def initialize(self, argv=None):
217 super(IPClusterEngines, self).initialize(argv)
217 super(IPClusterEngines, self).initialize(argv)
218 self.init_signal()
218 self.init_signal()
219 self.init_launchers()
219 self.init_launchers()
220
220
221 def init_launchers(self):
221 def init_launchers(self):
222 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
222 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
223 self.engine_launcher.on_stop(lambda r: self.loop.stop())
223 self.engine_launcher.on_stop(lambda r: self.loop.stop())
224
224
225 def init_signal(self):
225 def init_signal(self):
226 # Setup signals
226 # Setup signals
227 signal.signal(signal.SIGINT, self.sigint_handler)
227 signal.signal(signal.SIGINT, self.sigint_handler)
228
228
229 def build_launcher(self, clsname):
229 def build_launcher(self, clsname):
230 """import and instantiate a Launcher based on importstring"""
230 """import and instantiate a Launcher based on importstring"""
231 if '.' not in clsname:
231 if '.' not in clsname:
232 # not a module, presume it's the raw name in apps.launcher
232 # not a module, presume it's the raw name in apps.launcher
233 clsname = 'IPython.parallel.apps.launcher.'+clsname
233 clsname = 'IPython.parallel.apps.launcher.'+clsname
234 # print repr(clsname)
234 # print repr(clsname)
235 klass = import_item(clsname)
235 klass = import_item(clsname)
236
236
237 launcher = klass(
237 launcher = klass(
238 work_dir=self.profile_dir.location, config=self.config, log=self.log
238 work_dir=self.profile_dir.location, config=self.config, log=self.log
239 )
239 )
240 return launcher
240 return launcher
241
241
242 def start_engines(self):
242 def start_engines(self):
243 self.log.info("Starting %i engines"%self.n)
243 self.log.info("Starting %i engines"%self.n)
244 self.engine_launcher.start(
244 self.engine_launcher.start(
245 self.n,
245 self.n,
246 self.profile_dir.location
246 self.profile_dir.location
247 )
247 )
248
248
249 def stop_engines(self):
249 def stop_engines(self):
250 self.log.info("Stopping Engines...")
250 self.log.info("Stopping Engines...")
251 if self.engine_launcher.running:
251 if self.engine_launcher.running:
252 d = self.engine_launcher.stop()
252 d = self.engine_launcher.stop()
253 return d
253 return d
254 else:
254 else:
255 return None
255 return None
256
256
257 def stop_launchers(self, r=None):
257 def stop_launchers(self, r=None):
258 if not self._stopping:
258 if not self._stopping:
259 self._stopping = True
259 self._stopping = True
260 self.log.error("IPython cluster: stopping")
260 self.log.error("IPython cluster: stopping")
261 self.stop_engines()
261 self.stop_engines()
262 # Wait a few seconds to let things shut down.
262 # Wait a few seconds to let things shut down.
263 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
263 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
264 dc.start()
264 dc.start()
265
265
266 def sigint_handler(self, signum, frame):
266 def sigint_handler(self, signum, frame):
267 self.log.debug("SIGINT received, stopping launchers...")
267 self.log.debug("SIGINT received, stopping launchers...")
268 self.stop_launchers()
268 self.stop_launchers()
269
269
270 def start_logging(self):
270 def start_logging(self):
271 # Remove old log files of the controller and engine
271 # Remove old log files of the controller and engine
272 if self.clean_logs:
272 if self.clean_logs:
273 log_dir = self.profile_dir.log_dir
273 log_dir = self.profile_dir.log_dir
274 for f in os.listdir(log_dir):
274 for f in os.listdir(log_dir):
275 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
275 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
276 os.remove(os.path.join(log_dir, f))
276 os.remove(os.path.join(log_dir, f))
277 # This will remove old log files for ipcluster itself
277 # This will remove old log files for ipcluster itself
278 # super(IPBaseParallelApplication, self).start_logging()
278 # super(IPBaseParallelApplication, self).start_logging()
279
279
280 def start(self):
280 def start(self):
281 """Start the app for the engines subcommand."""
281 """Start the app for the engines subcommand."""
282 self.log.info("IPython cluster: started")
282 self.log.info("IPython cluster: started")
283 # First see if the cluster is already running
283 # First see if the cluster is already running
284
284
285 # Now log and daemonize
285 # Now log and daemonize
286 self.log.info(
286 self.log.info(
287 'Starting engines with [daemon=%r]' % self.daemonize
287 'Starting engines with [daemon=%r]' % self.daemonize
288 )
288 )
289 # TODO: Get daemonize working on Windows or as a Windows Server.
289 # TODO: Get daemonize working on Windows or as a Windows Server.
290 if self.daemonize:
290 if self.daemonize:
291 if os.name=='posix':
291 if os.name=='posix':
292 daemonize()
292 daemonize()
293
293
294 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
294 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
295 dc.start()
295 dc.start()
296 # Now write the new pid file AFTER our new forked pid is active.
296 # Now write the new pid file AFTER our new forked pid is active.
297 # self.write_pid_file()
297 # self.write_pid_file()
298 try:
298 try:
299 self.loop.start()
299 self.loop.start()
300 except KeyboardInterrupt:
300 except KeyboardInterrupt:
301 pass
301 pass
302 except zmq.ZMQError as e:
302 except zmq.ZMQError as e:
303 if e.errno == errno.EINTR:
303 if e.errno == errno.EINTR:
304 pass
304 pass
305 else:
305 else:
306 raise
306 raise
307
307
308 start_aliases = {}
308 start_aliases = {}
309 start_aliases.update(engine_aliases)
309 start_aliases.update(engine_aliases)
310 start_aliases.update(dict(
310 start_aliases.update(dict(
311 delay='IPClusterStart.delay',
311 delay='IPClusterStart.delay',
312 clean_logs='IPClusterStart.clean_logs',
312 clean_logs='IPClusterStart.clean_logs',
313 ))
313 ))
314
314
315 class IPClusterStart(IPClusterEngines):
315 class IPClusterStart(IPClusterEngines):
316
316
317 name = u'ipcluster'
317 name = u'ipcluster'
318 description = start_help
318 description = start_help
319 default_log_level = logging.INFO
319 default_log_level = logging.INFO
320 auto_create = Bool(True, config=True,
320 auto_create = Bool(True, config=True,
321 help="whether to create the profile_dir if it doesn't exist")
321 help="whether to create the profile_dir if it doesn't exist")
322 classes = List()
322 classes = List()
323 def _classes_default(self,):
323 def _classes_default(self,):
324 from IPython.parallel.apps import launcher
324 from IPython.parallel.apps import launcher
325 return [ProfileDir]+launcher.all_launchers
325 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
326
326
327 clean_logs = Bool(True, config=True,
327 clean_logs = Bool(True, config=True,
328 help="whether to cleanup old logs before starting")
328 help="whether to cleanup old logs before starting")
329
329
330 delay = CFloat(1., config=True,
330 delay = CFloat(1., config=True,
331 help="delay (in s) between starting the controller and the engines")
331 help="delay (in s) between starting the controller and the engines")
332
332
333 controller_launcher_class = Unicode('LocalControllerLauncher',
333 controller_launcher_class = Unicode('LocalControllerLauncher',
334 config=True,
334 config=True,
335 help="The class for launching a Controller."
335 help="The class for launching a Controller."
336 )
336 )
337 reset = Bool(False, config=True,
337 reset = Bool(False, config=True,
338 help="Whether to reset config files as part of '--create'."
338 help="Whether to reset config files as part of '--create'."
339 )
339 )
340
340
341 # flags = Dict(flags)
341 # flags = Dict(flags)
342 aliases = Dict(start_aliases)
342 aliases = Dict(start_aliases)
343
343
344 def init_launchers(self):
344 def init_launchers(self):
345 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
345 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
346 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
346 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
347 self.controller_launcher.on_stop(self.stop_launchers)
347 self.controller_launcher.on_stop(self.stop_launchers)
348
348
349 def start_controller(self):
349 def start_controller(self):
350 self.controller_launcher.start(
350 self.controller_launcher.start(
351 self.profile_dir.location
351 self.profile_dir.location
352 )
352 )
353
353
354 def stop_controller(self):
354 def stop_controller(self):
355 # self.log.info("In stop_controller")
355 # self.log.info("In stop_controller")
356 if self.controller_launcher and self.controller_launcher.running:
356 if self.controller_launcher and self.controller_launcher.running:
357 return self.controller_launcher.stop()
357 return self.controller_launcher.stop()
358
358
359 def stop_launchers(self, r=None):
359 def stop_launchers(self, r=None):
360 if not self._stopping:
360 if not self._stopping:
361 self.stop_controller()
361 self.stop_controller()
362 super(IPClusterStart, self).stop_launchers()
362 super(IPClusterStart, self).stop_launchers()
363
363
364 def start(self):
364 def start(self):
365 """Start the app for the start subcommand."""
365 """Start the app for the start subcommand."""
366 # First see if the cluster is already running
366 # First see if the cluster is already running
367 try:
367 try:
368 pid = self.get_pid_from_file()
368 pid = self.get_pid_from_file()
369 except PIDFileError:
369 except PIDFileError:
370 pass
370 pass
371 else:
371 else:
372 if self.check_pid(pid):
372 if self.check_pid(pid):
373 self.log.critical(
373 self.log.critical(
374 'Cluster is already running with [pid=%s]. '
374 'Cluster is already running with [pid=%s]. '
375 'use "ipcluster stop" to stop the cluster.' % pid
375 'use "ipcluster stop" to stop the cluster.' % pid
376 )
376 )
377 # Here I exit with a unusual exit status that other processes
377 # Here I exit with a unusual exit status that other processes
378 # can watch for to learn how I existed.
378 # can watch for to learn how I existed.
379 self.exit(ALREADY_STARTED)
379 self.exit(ALREADY_STARTED)
380 else:
380 else:
381 self.remove_pid_file()
381 self.remove_pid_file()
382
382
383
383
384 # Now log and daemonize
384 # Now log and daemonize
385 self.log.info(
385 self.log.info(
386 'Starting ipcluster with [daemon=%r]' % self.daemonize
386 'Starting ipcluster with [daemon=%r]' % self.daemonize
387 )
387 )
388 # TODO: Get daemonize working on Windows or as a Windows Server.
388 # TODO: Get daemonize working on Windows or as a Windows Server.
389 if self.daemonize:
389 if self.daemonize:
390 if os.name=='posix':
390 if os.name=='posix':
391 daemonize()
391 daemonize()
392
392
393 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
393 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
394 dc.start()
394 dc.start()
395 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
395 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
396 dc.start()
396 dc.start()
397 # Now write the new pid file AFTER our new forked pid is active.
397 # Now write the new pid file AFTER our new forked pid is active.
398 self.write_pid_file()
398 self.write_pid_file()
399 try:
399 try:
400 self.loop.start()
400 self.loop.start()
401 except KeyboardInterrupt:
401 except KeyboardInterrupt:
402 pass
402 pass
403 except zmq.ZMQError as e:
403 except zmq.ZMQError as e:
404 if e.errno == errno.EINTR:
404 if e.errno == errno.EINTR:
405 pass
405 pass
406 else:
406 else:
407 raise
407 raise
408 finally:
408 finally:
409 self.remove_pid_file()
409 self.remove_pid_file()
410
410
411 base='IPython.parallel.apps.ipclusterapp.IPCluster'
411 base='IPython.parallel.apps.ipclusterapp.IPCluster'
412
412
413 class IPBaseParallelApplication(Application):
413 class IPClusterApp(Application):
414 name = u'ipcluster'
414 name = u'ipcluster'
415 description = _description
415 description = _description
416
416
417 subcommands = {'create' : (base+'Create', create_help),
417 subcommands = {
418 'list' : (base+'List', list_help),
419 'start' : (base+'Start', start_help),
418 'start' : (base+'Start', start_help),
420 'stop' : (base+'Stop', stop_help),
419 'stop' : (base+'Stop', stop_help),
421 'engines' : (base+'Engines', engines_help),
420 'engines' : (base+'Engines', engines_help),
422 }
421 }
423
422
424 # no aliases or flags for parent App
423 # no aliases or flags for parent App
425 aliases = Dict()
424 aliases = Dict()
426 flags = Dict()
425 flags = Dict()
427
426
428 def start(self):
427 def start(self):
429 if self.subapp is None:
428 if self.subapp is None:
430 print "No subcommand specified! Must specify one of: %s"%(self.subcommands.keys())
429 print "No subcommand specified! Must specify one of: %s"%(self.subcommands.keys())
431 print
430 print
432 self.print_subcommands()
431 self.print_subcommands()
433 self.exit(1)
432 self.exit(1)
434 else:
433 else:
435 return self.subapp.start()
434 return self.subapp.start()
436
435
437 def launch_new_instance():
436 def launch_new_instance():
438 """Create and run the IPython cluster."""
437 """Create and run the IPython cluster."""
439 app = IPBaseParallelApplication.instance()
438 app = IPBaseParallelApplication.instance()
440 app.initialize()
439 app.initialize()
441 app.start()
440 app.start()
442
441
443
442
444 if __name__ == '__main__':
443 if __name__ == '__main__':
445 launch_new_instance()
444 launch_new_instance()
446
445
@@ -1,276 +1,276 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython engine application
4 The IPython engine application
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * MinRK
9 * MinRK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import json
24 import json
25 import os
25 import os
26 import sys
26 import sys
27
27
28 import zmq
28 import zmq
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30
30
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.parallel.apps.baseapp import BaseParallelApplication
32 from IPython.parallel.apps.baseapp import BaseParallelApplication
33 from IPython.zmq.log import EnginePUBHandler
33 from IPython.zmq.log import EnginePUBHandler
34
34
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.zmq.session import Session
36 from IPython.zmq.session import Session
37 from IPython.parallel.engine.engine import EngineFactory
37 from IPython.parallel.engine.engine import EngineFactory
38 from IPython.parallel.engine.streamkernel import Kernel
38 from IPython.parallel.engine.streamkernel import Kernel
39 from IPython.parallel.util import disambiguate_url
39 from IPython.parallel.util import disambiguate_url
40
40
41 from IPython.utils.importstring import import_item
41 from IPython.utils.importstring import import_item
42 from IPython.utils.traitlets import Bool, Unicode, Dict, List
42 from IPython.utils.traitlets import Bool, Unicode, Dict, List
43
43
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Module level variables
46 # Module level variables
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 #: The default config file name for this application
49 #: The default config file name for this application
50 default_config_file_name = u'ipengine_config.py'
50 default_config_file_name = u'ipengine_config.py'
51
51
52 _description = """Start an IPython engine for parallel computing.
52 _description = """Start an IPython engine for parallel computing.
53
53
54 IPython engines run in parallel and perform computations on behalf of a client
54 IPython engines run in parallel and perform computations on behalf of a client
55 and controller. A controller needs to be started before the engines. The
55 and controller. A controller needs to be started before the engines. The
56 engine can be configured using command line options or using a cluster
56 engine can be configured using command line options or using a cluster
57 directory. Cluster directories contain config, log and security files and are
57 directory. Cluster directories contain config, log and security files and are
58 usually located in your ipython directory and named as "profile_name".
58 usually located in your ipython directory and named as "profile_name".
59 See the `profile` and `profile_dir` options for details.
59 See the `profile` and `profile_dir` options for details.
60 """
60 """
61
61
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # MPI configuration
64 # MPI configuration
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 mpi4py_init = """from mpi4py import MPI as mpi
67 mpi4py_init = """from mpi4py import MPI as mpi
68 mpi.size = mpi.COMM_WORLD.Get_size()
68 mpi.size = mpi.COMM_WORLD.Get_size()
69 mpi.rank = mpi.COMM_WORLD.Get_rank()
69 mpi.rank = mpi.COMM_WORLD.Get_rank()
70 """
70 """
71
71
72
72
73 pytrilinos_init = """from PyTrilinos import Epetra
73 pytrilinos_init = """from PyTrilinos import Epetra
74 class SimpleStruct:
74 class SimpleStruct:
75 pass
75 pass
76 mpi = SimpleStruct()
76 mpi = SimpleStruct()
77 mpi.rank = 0
77 mpi.rank = 0
78 mpi.size = 0
78 mpi.size = 0
79 """
79 """
80
80
81 class MPI(Configurable):
81 class MPI(Configurable):
82 """Configurable for MPI initialization"""
82 """Configurable for MPI initialization"""
83 use = Unicode('', config=True,
83 use = Unicode('', config=True,
84 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
84 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
85 )
85 )
86
86
87 def _on_use_changed(self, old, new):
87 def _on_use_changed(self, old, new):
88 # load default init script if it's not set
88 # load default init script if it's not set
89 if not self.init_script:
89 if not self.init_script:
90 self.init_script = self.default_inits.get(new, '')
90 self.init_script = self.default_inits.get(new, '')
91
91
92 init_script = Unicode('', config=True,
92 init_script = Unicode('', config=True,
93 help="Initialization code for MPI")
93 help="Initialization code for MPI")
94
94
95 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
95 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
96 config=True)
96 config=True)
97
97
98
98
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100 # Main application
100 # Main application
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102
102
103
103
104 class IPEngineApp(BaseParallelApplication):
104 class IPEngineApp(BaseParallelApplication):
105
105
106 app_name = Unicode(u'ipengine')
106 name = Unicode(u'ipengine')
107 description = Unicode(_description)
107 description = Unicode(_description)
108 config_file_name = Unicode(default_config_file_name)
108 config_file_name = Unicode(default_config_file_name)
109 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
109 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
110
110
111 startup_script = Unicode(u'', config=True,
111 startup_script = Unicode(u'', config=True,
112 help='specify a script to be run at startup')
112 help='specify a script to be run at startup')
113 startup_command = Unicode('', config=True,
113 startup_command = Unicode('', config=True,
114 help='specify a command to be run at startup')
114 help='specify a command to be run at startup')
115
115
116 url_file = Unicode(u'', config=True,
116 url_file = Unicode(u'', config=True,
117 help="""The full location of the file containing the connection information for
117 help="""The full location of the file containing the connection information for
118 the controller. If this is not given, the file must be in the
118 the controller. If this is not given, the file must be in the
119 security directory of the cluster directory. This location is
119 security directory of the cluster directory. This location is
120 resolved using the `profile` or `profile_dir` options.""",
120 resolved using the `profile` or `profile_dir` options.""",
121 )
121 )
122
122
123 url_file_name = Unicode(u'ipcontroller-engine.json')
123 url_file_name = Unicode(u'ipcontroller-engine.json')
124 log_url = Unicode('', config=True,
124 log_url = Unicode('', config=True,
125 help="""The URL for the iploggerapp instance, for forwarding
125 help="""The URL for the iploggerapp instance, for forwarding
126 logging to a central location.""")
126 logging to a central location.""")
127
127
128 aliases = Dict(dict(
128 aliases = Dict(dict(
129 file = 'IPEngineApp.url_file',
129 file = 'IPEngineApp.url_file',
130 c = 'IPEngineApp.startup_command',
130 c = 'IPEngineApp.startup_command',
131 s = 'IPEngineApp.startup_script',
131 s = 'IPEngineApp.startup_script',
132
132
133 ident = 'Session.session',
133 ident = 'Session.session',
134 user = 'Session.username',
134 user = 'Session.username',
135 exec_key = 'Session.keyfile',
135 exec_key = 'Session.keyfile',
136
136
137 url = 'EngineFactory.url',
137 url = 'EngineFactory.url',
138 ip = 'EngineFactory.ip',
138 ip = 'EngineFactory.ip',
139 transport = 'EngineFactory.transport',
139 transport = 'EngineFactory.transport',
140 port = 'EngineFactory.regport',
140 port = 'EngineFactory.regport',
141 location = 'EngineFactory.location',
141 location = 'EngineFactory.location',
142
142
143 timeout = 'EngineFactory.timeout',
143 timeout = 'EngineFactory.timeout',
144
144
145 profile = "IPEngineApp.profile",
145 profile = "IPEngineApp.profile",
146 profile_dir = 'ProfileDir.location',
146 profile_dir = 'ProfileDir.location',
147
147
148 mpi = 'MPI.use',
148 mpi = 'MPI.use',
149
149
150 log_level = 'IPEngineApp.log_level',
150 log_level = 'IPEngineApp.log_level',
151 log_url = 'IPEngineApp.log_url'
151 log_url = 'IPEngineApp.log_url'
152 ))
152 ))
153
153
154 # def find_key_file(self):
154 # def find_key_file(self):
155 # """Set the key file.
155 # """Set the key file.
156 #
156 #
157 # Here we don't try to actually see if it exists for is valid as that
157 # Here we don't try to actually see if it exists for is valid as that
158 # is hadled by the connection logic.
158 # is hadled by the connection logic.
159 # """
159 # """
160 # config = self.master_config
160 # config = self.master_config
161 # # Find the actual controller key file
161 # # Find the actual controller key file
162 # if not config.Global.key_file:
162 # if not config.Global.key_file:
163 # try_this = os.path.join(
163 # try_this = os.path.join(
164 # config.Global.profile_dir,
164 # config.Global.profile_dir,
165 # config.Global.security_dir,
165 # config.Global.security_dir,
166 # config.Global.key_file_name
166 # config.Global.key_file_name
167 # )
167 # )
168 # config.Global.key_file = try_this
168 # config.Global.key_file = try_this
169
169
170 def find_url_file(self):
170 def find_url_file(self):
171 """Set the key file.
171 """Set the key file.
172
172
173 Here we don't try to actually see if it exists for is valid as that
173 Here we don't try to actually see if it exists for is valid as that
174 is hadled by the connection logic.
174 is hadled by the connection logic.
175 """
175 """
176 config = self.config
176 config = self.config
177 # Find the actual controller key file
177 # Find the actual controller key file
178 if not self.url_file:
178 if not self.url_file:
179 self.url_file = os.path.join(
179 self.url_file = os.path.join(
180 self.profile_dir.security_dir,
180 self.profile_dir.security_dir,
181 self.url_file_name
181 self.url_file_name
182 )
182 )
183 def init_engine(self):
183 def init_engine(self):
184 # This is the working dir by now.
184 # This is the working dir by now.
185 sys.path.insert(0, '')
185 sys.path.insert(0, '')
186 config = self.config
186 config = self.config
187 # print config
187 # print config
188 self.find_url_file()
188 self.find_url_file()
189
189
190 # if os.path.exists(config.Global.key_file) and config.Global.secure:
190 # if os.path.exists(config.Global.key_file) and config.Global.secure:
191 # config.SessionFactory.exec_key = config.Global.key_file
191 # config.SessionFactory.exec_key = config.Global.key_file
192 if os.path.exists(self.url_file):
192 if os.path.exists(self.url_file):
193 with open(self.url_file) as f:
193 with open(self.url_file) as f:
194 d = json.loads(f.read())
194 d = json.loads(f.read())
195 for k,v in d.iteritems():
195 for k,v in d.iteritems():
196 if isinstance(v, unicode):
196 if isinstance(v, unicode):
197 d[k] = v.encode()
197 d[k] = v.encode()
198 if d['exec_key']:
198 if d['exec_key']:
199 config.Session.key = d['exec_key']
199 config.Session.key = d['exec_key']
200 d['url'] = disambiguate_url(d['url'], d['location'])
200 d['url'] = disambiguate_url(d['url'], d['location'])
201 config.EngineFactory.url = d['url']
201 config.EngineFactory.url = d['url']
202 config.EngineFactory.location = d['location']
202 config.EngineFactory.location = d['location']
203
203
204 try:
204 try:
205 exec_lines = config.Kernel.exec_lines
205 exec_lines = config.Kernel.exec_lines
206 except AttributeError:
206 except AttributeError:
207 config.Kernel.exec_lines = []
207 config.Kernel.exec_lines = []
208 exec_lines = config.Kernel.exec_lines
208 exec_lines = config.Kernel.exec_lines
209
209
210 if self.startup_script:
210 if self.startup_script:
211 enc = sys.getfilesystemencoding() or 'utf8'
211 enc = sys.getfilesystemencoding() or 'utf8'
212 cmd="execfile(%r)"%self.startup_script.encode(enc)
212 cmd="execfile(%r)"%self.startup_script.encode(enc)
213 exec_lines.append(cmd)
213 exec_lines.append(cmd)
214 if self.startup_command:
214 if self.startup_command:
215 exec_lines.append(self.startup_command)
215 exec_lines.append(self.startup_command)
216
216
217 # Create the underlying shell class and Engine
217 # Create the underlying shell class and Engine
218 # shell_class = import_item(self.master_config.Global.shell_class)
218 # shell_class = import_item(self.master_config.Global.shell_class)
219 # print self.config
219 # print self.config
220 try:
220 try:
221 self.engine = EngineFactory(config=config, log=self.log)
221 self.engine = EngineFactory(config=config, log=self.log)
222 except:
222 except:
223 self.log.error("Couldn't start the Engine", exc_info=True)
223 self.log.error("Couldn't start the Engine", exc_info=True)
224 self.exit(1)
224 self.exit(1)
225
225
226 def forward_logging(self):
226 def forward_logging(self):
227 if self.log_url:
227 if self.log_url:
228 self.log.info("Forwarding logging to %s"%self.log_url)
228 self.log.info("Forwarding logging to %s"%self.log_url)
229 context = self.engine.context
229 context = self.engine.context
230 lsock = context.socket(zmq.PUB)
230 lsock = context.socket(zmq.PUB)
231 lsock.connect(self.log_url)
231 lsock.connect(self.log_url)
232 self.log.removeHandler(self._log_handler)
232 self.log.removeHandler(self._log_handler)
233 handler = EnginePUBHandler(self.engine, lsock)
233 handler = EnginePUBHandler(self.engine, lsock)
234 handler.setLevel(self.log_level)
234 handler.setLevel(self.log_level)
235 self.log.addHandler(handler)
235 self.log.addHandler(handler)
236 self._log_handler = handler
236 self._log_handler = handler
237 #
237 #
238 def init_mpi(self):
238 def init_mpi(self):
239 global mpi
239 global mpi
240 self.mpi = MPI(config=self.config)
240 self.mpi = MPI(config=self.config)
241
241
242 mpi_import_statement = self.mpi.init_script
242 mpi_import_statement = self.mpi.init_script
243 if mpi_import_statement:
243 if mpi_import_statement:
244 try:
244 try:
245 self.log.info("Initializing MPI:")
245 self.log.info("Initializing MPI:")
246 self.log.info(mpi_import_statement)
246 self.log.info(mpi_import_statement)
247 exec mpi_import_statement in globals()
247 exec mpi_import_statement in globals()
248 except:
248 except:
249 mpi = None
249 mpi = None
250 else:
250 else:
251 mpi = None
251 mpi = None
252
252
253 def initialize(self, argv=None):
253 def initialize(self, argv=None):
254 super(IPEngineApp, self).initialize(argv)
254 super(IPEngineApp, self).initialize(argv)
255 self.init_mpi()
255 self.init_mpi()
256 self.init_engine()
256 self.init_engine()
257 self.forward_logging()
257 self.forward_logging()
258
258
259 def start(self):
259 def start(self):
260 self.engine.start()
260 self.engine.start()
261 try:
261 try:
262 self.engine.loop.start()
262 self.engine.loop.start()
263 except KeyboardInterrupt:
263 except KeyboardInterrupt:
264 self.log.critical("Engine Interrupted, shutting down...\n")
264 self.log.critical("Engine Interrupted, shutting down...\n")
265
265
266
266
267 def launch_new_instance():
267 def launch_new_instance():
268 """Create and run the IPython engine"""
268 """Create and run the IPython engine"""
269 app = IPEngineApp.instance()
269 app = IPEngineApp.instance()
270 app.initialize()
270 app.initialize()
271 app.start()
271 app.start()
272
272
273
273
274 if __name__ == '__main__':
274 if __name__ == '__main__':
275 launch_new_instance()
275 launch_new_instance()
276
276
@@ -1,101 +1,101 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A simple IPython logger application
4 A simple IPython logger application
5
5
6 Authors:
6 Authors:
7
7
8 * MinRK
8 * MinRK
9
9
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2011 The IPython Development Team
13 # Copyright (C) 2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import sys
24 import sys
25
25
26 import zmq
26 import zmq
27
27
28 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
29 from IPython.utils.traitlets import Bool, Dict, Unicode
29 from IPython.utils.traitlets import Bool, Dict, Unicode
30
30
31 from IPython.parallel.apps.baseapp import (
31 from IPython.parallel.apps.baseapp import (
32 BaseParallelApplication,
32 BaseParallelApplication,
33 base_aliases
33 base_aliases
34 )
34 )
35 from IPython.parallel.apps.logwatcher import LogWatcher
35 from IPython.parallel.apps.logwatcher import LogWatcher
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Module level variables
38 # Module level variables
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 #: The default config file name for this application
41 #: The default config file name for this application
42 default_config_file_name = u'iplogger_config.py'
42 default_config_file_name = u'iplogger_config.py'
43
43
44 _description = """Start an IPython logger for parallel computing.
44 _description = """Start an IPython logger for parallel computing.
45
45
46 IPython controllers and engines (and your own processes) can broadcast log messages
46 IPython controllers and engines (and your own processes) can broadcast log messages
47 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
47 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
48 logger can be configured using command line options or using a cluster
48 logger can be configured using command line options or using a cluster
49 directory. Cluster directories contain config, log and security files and are
49 directory. Cluster directories contain config, log and security files and are
50 usually located in your ipython directory and named as "profile_name".
50 usually located in your ipython directory and named as "profile_name".
51 See the `profile` and `profile_dir` options for details.
51 See the `profile` and `profile_dir` options for details.
52 """
52 """
53
53
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Main application
56 # Main application
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 aliases = {}
58 aliases = {}
59 aliases.update(base_aliases)
59 aliases.update(base_aliases)
60 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
60 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
61
61
62 class IPLoggerApp(BaseParallelApplication):
62 class IPLoggerApp(BaseParallelApplication):
63
63
64 name = u'iploggerz'
64 name = u'iplogger'
65 description = _description
65 description = _description
66 config_file_name = Unicode(default_config_file_name)
66 config_file_name = Unicode(default_config_file_name)
67
67
68 classes = [LogWatcher, ProfileDir]
68 classes = [LogWatcher, ProfileDir]
69 aliases = Dict(aliases)
69 aliases = Dict(aliases)
70
70
71 def initialize(self, argv=None):
71 def initialize(self, argv=None):
72 super(IPLoggerApp, self).initialize(argv)
72 super(IPLoggerApp, self).initialize(argv)
73 self.init_watcher()
73 self.init_watcher()
74
74
75 def init_watcher(self):
75 def init_watcher(self):
76 try:
76 try:
77 self.watcher = LogWatcher(config=self.config, log=self.log)
77 self.watcher = LogWatcher(config=self.config, log=self.log)
78 except:
78 except:
79 self.log.error("Couldn't start the LogWatcher", exc_info=True)
79 self.log.error("Couldn't start the LogWatcher", exc_info=True)
80 self.exit(1)
80 self.exit(1)
81 self.log.info("Listening for log messages on %r"%self.watcher.url)
81 self.log.info("Listening for log messages on %r"%self.watcher.url)
82
82
83
83
84 def start(self):
84 def start(self):
85 self.watcher.start()
85 self.watcher.start()
86 try:
86 try:
87 self.watcher.loop.start()
87 self.watcher.loop.start()
88 except KeyboardInterrupt:
88 except KeyboardInterrupt:
89 self.log.critical("Logging Interrupted, shutting down...\n")
89 self.log.critical("Logging Interrupted, shutting down...\n")
90
90
91
91
92 def launch_new_instance():
92 def launch_new_instance():
93 """Create and run the IPython LogWatcher"""
93 """Create and run the IPython LogWatcher"""
94 app = IPLoggerApp.instance()
94 app = IPLoggerApp.instance()
95 app.initialize()
95 app.initialize()
96 app.start()
96 app.start()
97
97
98
98
99 if __name__ == '__main__':
99 if __name__ == '__main__':
100 launch_new_instance()
100 launch_new_instance()
101
101
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now