##// END OF EJS Templates
More work on adding examples to help strings.
Brian Granger -
Show More
@@ -1,231 +1,237 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 IPython profile by name
39 create_help = """Create an IPython 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 profile directory by name,
48 profile directory by 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 _list_examples = "ipython profile list # list all profiles"
78
79 _create_examples = """
80 ipython profile create foo # create profile foo
81 ipython profile create foo --init # create with default config files
82 """
83
84 _main_examples = """
85 ipython profile create -h # show the help string for the create subcommand
86 ipython profile list -h # show the help string for the list subcommand
87 """
88
77 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
78 # Profile Application Class (for `ipython profile` subcommand)
90 # Profile Application Class (for `ipython profile` subcommand)
79 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
80
92
81
93
82 class ProfileList(Application):
94 class ProfileList(Application):
83 name = u'ipython-profile'
95 name = u'ipython-profile'
84 description = list_help
96 description = list_help
85
97 examples = _list_examples
98
86 aliases = Dict({
99 aliases = Dict({
87 'ipython-dir' : 'ProfileList.ipython_dir',
100 'ipython-dir' : 'ProfileList.ipython_dir',
88 'log-level' : 'Application.log_level',
101 'log-level' : 'Application.log_level',
89 })
102 })
90 flags = Dict(dict(
103 flags = Dict(dict(
91 debug = ({'Application' : {'log_level' : 0}},
104 debug = ({'Application' : {'log_level' : 0}},
92 "Set Application.log_level to 0, maximizing log output."
105 "Set Application.log_level to 0, maximizing log output."
93 )
106 )
94 ))
107 ))
108
95 ipython_dir = Unicode(get_ipython_dir(), config=True,
109 ipython_dir = Unicode(get_ipython_dir(), config=True,
96 help="""
110 help="""
97 The name of the IPython directory. This directory is used for logging
111 The name of the IPython directory. This directory is used for logging
98 configuration (through profiles), history storage, etc. The default
112 configuration (through profiles), history storage, etc. The default
99 is usually $HOME/.ipython. This options can also be specified through
113 is usually $HOME/.ipython. This options can also be specified through
100 the environment variable IPYTHON_DIR.
114 the environment variable IPYTHON_DIR.
101 """
115 """
102 )
116 )
103
117
104 def list_profile_dirs(self):
118 def list_profile_dirs(self):
105 # Find the search paths
119 # Find the search paths
106 paths = [os.getcwdu(), self.ipython_dir]
120 paths = [os.getcwdu(), self.ipython_dir]
107
121
108 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
122 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
109 for path in paths:
123 for path in paths:
110 files = os.listdir(path)
124 files = os.listdir(path)
111 for f in files:
125 for f in files:
112 full_path = os.path.join(path, f)
126 full_path = os.path.join(path, f)
113 if os.path.isdir(full_path) and f.startswith('profile_'):
127 if os.path.isdir(full_path) and f.startswith('profile_'):
114 profile = f.split('_',1)[-1]
128 profile = f.split('_',1)[-1]
115 start_cmd = 'ipython profile=%s' % profile
129 start_cmd = 'ipython profile=%s' % profile
116 print start_cmd + " ==> " + full_path
130 print start_cmd + " ==> " + full_path
117
131
118 def start(self):
132 def start(self):
119 self.list_profile_dirs()
133 self.list_profile_dirs()
120
134
121
135
122 create_flags = {}
136 create_flags = {}
123 create_flags.update(base_flags)
137 create_flags.update(base_flags)
124 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
138 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
125 "reset config files to defaults", "leave existing config files"))
139 "reset config files to defaults", "leave existing config files"))
126 create_flags.update(boolean_flag('parallel', 'ProfileCreate.parallel',
140 create_flags.update(boolean_flag('parallel', 'ProfileCreate.parallel',
127 "Include parallel computing config files",
141 "Include parallel computing config files",
128 "Don't include parallel computing config files"))
142 "Don't include parallel computing config files"))
129
143
130 create_examples = """
131 ipython profile create foo # create profile foo
132 ipython profile create foo --init # create with default config files
133 """
134
144
135 class ProfileCreate(BaseIPythonApplication):
145 class ProfileCreate(BaseIPythonApplication):
136 name = u'ipython-profile'
146 name = u'ipython-profile'
137 description = create_help
147 description = create_help
138 examples = create_examples
148 examples = _create_examples
139 auto_create = Bool(True, config=False)
149 auto_create = Bool(True, config=False)
140
150
141 def _copy_config_files_default(self):
151 def _copy_config_files_default(self):
142 return True
152 return True
143
153
144 parallel = Bool(False, config=True,
154 parallel = Bool(False, config=True,
145 help="whether to include parallel computing config files")
155 help="whether to include parallel computing config files")
146 def _parallel_changed(self, name, old, new):
156 def _parallel_changed(self, name, old, new):
147 parallel_files = [ 'ipcontroller_config.py',
157 parallel_files = [ 'ipcontroller_config.py',
148 'ipengine_config.py',
158 'ipengine_config.py',
149 'ipcluster_config.py'
159 'ipcluster_config.py'
150 ]
160 ]
151 if new:
161 if new:
152 for cf in parallel_files:
162 for cf in parallel_files:
153 self.config_files.append(cf)
163 self.config_files.append(cf)
154 else:
164 else:
155 for cf in parallel_files:
165 for cf in parallel_files:
156 if cf in self.config_files:
166 if cf in self.config_files:
157 self.config_files.remove(cf)
167 self.config_files.remove(cf)
158
168
159 def parse_command_line(self, argv):
169 def parse_command_line(self, argv):
160 super(ProfileCreate, self).parse_command_line(argv)
170 super(ProfileCreate, self).parse_command_line(argv)
161 # accept positional arg as profile name
171 # accept positional arg as profile name
162 if self.extra_args:
172 if self.extra_args:
163 self.profile = self.extra_args[0]
173 self.profile = self.extra_args[0]
164
174
165 flags = Dict(create_flags)
175 flags = Dict(create_flags)
166
176
167 classes = [ProfileDir]
177 classes = [ProfileDir]
168
178
169 def init_config_files(self):
179 def init_config_files(self):
170 super(ProfileCreate, self).init_config_files()
180 super(ProfileCreate, self).init_config_files()
171 # use local imports, since these classes may import from here
181 # use local imports, since these classes may import from here
172 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
182 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
173 apps = [TerminalIPythonApp]
183 apps = [TerminalIPythonApp]
174 try:
184 try:
175 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
185 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
176 except Exception:
186 except Exception:
177 # this should be ImportError, but under weird circumstances
187 # this should be ImportError, but under weird circumstances
178 # this might be an AttributeError, or possibly others
188 # this might be an AttributeError, or possibly others
179 # in any case, nothing should cause the profile creation to crash.
189 # in any case, nothing should cause the profile creation to crash.
180 pass
190 pass
181 else:
191 else:
182 apps.append(IPythonQtConsoleApp)
192 apps.append(IPythonQtConsoleApp)
183 if self.parallel:
193 if self.parallel:
184 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
194 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
185 from IPython.parallel.apps.ipengineapp import IPEngineApp
195 from IPython.parallel.apps.ipengineapp import IPEngineApp
186 from IPython.parallel.apps.ipclusterapp import IPClusterStart
196 from IPython.parallel.apps.ipclusterapp import IPClusterStart
187 from IPython.parallel.apps.iploggerapp import IPLoggerApp
197 from IPython.parallel.apps.iploggerapp import IPLoggerApp
188 apps.extend([
198 apps.extend([
189 IPControllerApp,
199 IPControllerApp,
190 IPEngineApp,
200 IPEngineApp,
191 IPClusterStart,
201 IPClusterStart,
192 IPLoggerApp,
202 IPLoggerApp,
193 ])
203 ])
194 for App in apps:
204 for App in apps:
195 app = App()
205 app = App()
196 app.config.update(self.config)
206 app.config.update(self.config)
197 app.log = self.log
207 app.log = self.log
198 app.overwrite = self.overwrite
208 app.overwrite = self.overwrite
199 app.copy_config_files=True
209 app.copy_config_files=True
200 app.profile = self.profile
210 app.profile = self.profile
201 app.init_profile_dir()
211 app.init_profile_dir()
202 app.init_config_files()
212 app.init_config_files()
203
213
204 def stage_default_config_file(self):
214 def stage_default_config_file(self):
205 pass
215 pass
206
216
207 main_examples = """
208 ipython profile create -h # show the help string for the create subcommand
209 ipython profile list -h # show the help string for the list subcommand
210 """
211
217
212 class ProfileApp(Application):
218 class ProfileApp(Application):
213 name = u'ipython-profile'
219 name = u'ipython-profile'
214 description = profile_help
220 description = profile_help
215 examples = main_examples
221 examples = _main_examples
216
222
217 subcommands = Dict(dict(
223 subcommands = Dict(dict(
218 create = (ProfileCreate, "Create a new profile dir with default config files"),
224 create = (ProfileCreate, "Create a new profile dir with default config files"),
219 list = (ProfileList, "List existing profiles")
225 list = (ProfileList, "List existing profiles")
220 ))
226 ))
221
227
222 def start(self):
228 def start(self):
223 if self.subapp is None:
229 if self.subapp is None:
224 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
230 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
225 print
231 print
226 self.print_description()
232 self.print_description()
227 self.print_subcommands()
233 self.print_subcommands()
228 self.exit(1)
234 self.exit(1)
229 else:
235 else:
230 return self.subapp.start()
236 return self.subapp.start()
231
237
@@ -1,429 +1,434 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12
12
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib imports
19 # stdlib imports
20 import os
20 import os
21 import signal
21 import signal
22 import sys
22 import sys
23
23
24 # System library imports
24 # System library imports
25 from IPython.external.qt import QtGui
25 from IPython.external.qt import QtGui
26 from pygments.styles import get_all_styles
26 from pygments.styles import get_all_styles
27
27
28 # Local imports
28 # Local imports
29 from IPython.config.application import boolean_flag
29 from IPython.config.application import boolean_flag
30 from IPython.core.application import BaseIPythonApplication
30 from IPython.core.application import BaseIPythonApplication
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
32 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
33 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
33 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
34 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
34 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
35 from IPython.frontend.qt.console import styles
35 from IPython.frontend.qt.console import styles
36 from IPython.frontend.qt.kernelmanager import QtKernelManager
36 from IPython.frontend.qt.kernelmanager import QtKernelManager
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
38 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
39 )
39 )
40 from IPython.zmq.ipkernel import (
40 from IPython.zmq.ipkernel import (
41 flags as ipkernel_flags,
41 flags as ipkernel_flags,
42 aliases as ipkernel_aliases,
42 aliases as ipkernel_aliases,
43 IPKernelApp
43 IPKernelApp
44 )
44 )
45 from IPython.zmq.session import Session
45 from IPython.zmq.session import Session
46 from IPython.zmq.zmqshell import ZMQInteractiveShell
46 from IPython.zmq.zmqshell import ZMQInteractiveShell
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Network Constants
50 # Network Constants
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
53 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Globals
57 #-----------------------------------------------------------------------------
58
59 _examples = """
60 ipython qtconsole # start the qtconsole
61 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
62 """
63
64 #-----------------------------------------------------------------------------
56 # Classes
65 # Classes
57 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
58
67
59 class MainWindow(QtGui.QMainWindow):
68 class MainWindow(QtGui.QMainWindow):
60
69
61 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
62 # 'object' interface
71 # 'object' interface
63 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
64
73
65 def __init__(self, app, frontend, existing=False, may_close=True,
74 def __init__(self, app, frontend, existing=False, may_close=True,
66 confirm_exit=True):
75 confirm_exit=True):
67 """ Create a MainWindow for the specified FrontendWidget.
76 """ Create a MainWindow for the specified FrontendWidget.
68
77
69 The app is passed as an argument to allow for different
78 The app is passed as an argument to allow for different
70 closing behavior depending on whether we are the Kernel's parent.
79 closing behavior depending on whether we are the Kernel's parent.
71
80
72 If existing is True, then this Console does not own the Kernel.
81 If existing is True, then this Console does not own the Kernel.
73
82
74 If may_close is True, then this Console is permitted to close the kernel
83 If may_close is True, then this Console is permitted to close the kernel
75 """
84 """
76 super(MainWindow, self).__init__()
85 super(MainWindow, self).__init__()
77 self._app = app
86 self._app = app
78 self._frontend = frontend
87 self._frontend = frontend
79 self._existing = existing
88 self._existing = existing
80 if existing:
89 if existing:
81 self._may_close = may_close
90 self._may_close = may_close
82 else:
91 else:
83 self._may_close = True
92 self._may_close = True
84 self._frontend.exit_requested.connect(self.close)
93 self._frontend.exit_requested.connect(self.close)
85 self._confirm_exit = confirm_exit
94 self._confirm_exit = confirm_exit
86 self.setCentralWidget(frontend)
95 self.setCentralWidget(frontend)
87
96
88 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
89 # QWidget interface
98 # QWidget interface
90 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
91
100
92 def closeEvent(self, event):
101 def closeEvent(self, event):
93 """ Close the window and the kernel (if necessary).
102 """ Close the window and the kernel (if necessary).
94
103
95 This will prompt the user if they are finished with the kernel, and if
104 This will prompt the user if they are finished with the kernel, and if
96 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
105 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
97 it closes without prompt.
106 it closes without prompt.
98 """
107 """
99 keepkernel = None #Use the prompt by default
108 keepkernel = None #Use the prompt by default
100 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
109 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
101 keepkernel = self._frontend._keep_kernel_on_exit
110 keepkernel = self._frontend._keep_kernel_on_exit
102
111
103 kernel_manager = self._frontend.kernel_manager
112 kernel_manager = self._frontend.kernel_manager
104
113
105 if keepkernel is None and not self._confirm_exit:
114 if keepkernel is None and not self._confirm_exit:
106 # don't prompt, just terminate the kernel if we own it
115 # don't prompt, just terminate the kernel if we own it
107 # or leave it alone if we don't
116 # or leave it alone if we don't
108 keepkernel = not self._existing
117 keepkernel = not self._existing
109
118
110 if keepkernel is None: #show prompt
119 if keepkernel is None: #show prompt
111 if kernel_manager and kernel_manager.channels_running:
120 if kernel_manager and kernel_manager.channels_running:
112 title = self.window().windowTitle()
121 title = self.window().windowTitle()
113 cancel = QtGui.QMessageBox.Cancel
122 cancel = QtGui.QMessageBox.Cancel
114 okay = QtGui.QMessageBox.Ok
123 okay = QtGui.QMessageBox.Ok
115 if self._may_close:
124 if self._may_close:
116 msg = "You are closing this Console window."
125 msg = "You are closing this Console window."
117 info = "Would you like to quit the Kernel and all attached Consoles as well?"
126 info = "Would you like to quit the Kernel and all attached Consoles as well?"
118 justthis = QtGui.QPushButton("&No, just this Console", self)
127 justthis = QtGui.QPushButton("&No, just this Console", self)
119 justthis.setShortcut('N')
128 justthis.setShortcut('N')
120 closeall = QtGui.QPushButton("&Yes, quit everything", self)
129 closeall = QtGui.QPushButton("&Yes, quit everything", self)
121 closeall.setShortcut('Y')
130 closeall.setShortcut('Y')
122 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
131 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
123 title, msg)
132 title, msg)
124 box.setInformativeText(info)
133 box.setInformativeText(info)
125 box.addButton(cancel)
134 box.addButton(cancel)
126 box.addButton(justthis, QtGui.QMessageBox.NoRole)
135 box.addButton(justthis, QtGui.QMessageBox.NoRole)
127 box.addButton(closeall, QtGui.QMessageBox.YesRole)
136 box.addButton(closeall, QtGui.QMessageBox.YesRole)
128 box.setDefaultButton(closeall)
137 box.setDefaultButton(closeall)
129 box.setEscapeButton(cancel)
138 box.setEscapeButton(cancel)
130 reply = box.exec_()
139 reply = box.exec_()
131 if reply == 1: # close All
140 if reply == 1: # close All
132 kernel_manager.shutdown_kernel()
141 kernel_manager.shutdown_kernel()
133 #kernel_manager.stop_channels()
142 #kernel_manager.stop_channels()
134 event.accept()
143 event.accept()
135 elif reply == 0: # close Console
144 elif reply == 0: # close Console
136 if not self._existing:
145 if not self._existing:
137 # Have kernel: don't quit, just close the window
146 # Have kernel: don't quit, just close the window
138 self._app.setQuitOnLastWindowClosed(False)
147 self._app.setQuitOnLastWindowClosed(False)
139 self.deleteLater()
148 self.deleteLater()
140 event.accept()
149 event.accept()
141 else:
150 else:
142 event.ignore()
151 event.ignore()
143 else:
152 else:
144 reply = QtGui.QMessageBox.question(self, title,
153 reply = QtGui.QMessageBox.question(self, title,
145 "Are you sure you want to close this Console?"+
154 "Are you sure you want to close this Console?"+
146 "\nThe Kernel and other Consoles will remain active.",
155 "\nThe Kernel and other Consoles will remain active.",
147 okay|cancel,
156 okay|cancel,
148 defaultButton=okay
157 defaultButton=okay
149 )
158 )
150 if reply == okay:
159 if reply == okay:
151 event.accept()
160 event.accept()
152 else:
161 else:
153 event.ignore()
162 event.ignore()
154 elif keepkernel: #close console but leave kernel running (no prompt)
163 elif keepkernel: #close console but leave kernel running (no prompt)
155 if kernel_manager and kernel_manager.channels_running:
164 if kernel_manager and kernel_manager.channels_running:
156 if not self._existing:
165 if not self._existing:
157 # I have the kernel: don't quit, just close the window
166 # I have the kernel: don't quit, just close the window
158 self._app.setQuitOnLastWindowClosed(False)
167 self._app.setQuitOnLastWindowClosed(False)
159 event.accept()
168 event.accept()
160 else: #close console and kernel (no prompt)
169 else: #close console and kernel (no prompt)
161 if kernel_manager and kernel_manager.channels_running:
170 if kernel_manager and kernel_manager.channels_running:
162 kernel_manager.shutdown_kernel()
171 kernel_manager.shutdown_kernel()
163 event.accept()
172 event.accept()
164
173
165 #-----------------------------------------------------------------------------
174 #-----------------------------------------------------------------------------
166 # Aliases and Flags
175 # Aliases and Flags
167 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
168
177
169 flags = dict(ipkernel_flags)
178 flags = dict(ipkernel_flags)
170
179
171 flags.update({
180 flags.update({
172 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
181 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
173 "Connect to an existing kernel."),
182 "Connect to an existing kernel."),
174 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
183 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
175 "Use a pure Python kernel instead of an IPython kernel."),
184 "Use a pure Python kernel instead of an IPython kernel."),
176 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
185 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
177 "Disable rich text support."),
186 "Disable rich text support."),
178 })
187 })
179 flags.update(boolean_flag(
188 flags.update(boolean_flag(
180 'gui-completion', 'ConsoleWidget.gui_completion',
189 'gui-completion', 'ConsoleWidget.gui_completion',
181 "use a GUI widget for tab completion",
190 "use a GUI widget for tab completion",
182 "use plaintext output for completion"
191 "use plaintext output for completion"
183 ))
192 ))
184 flags.update(boolean_flag(
193 flags.update(boolean_flag(
185 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
194 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
186 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
195 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.
196 to force a direct exit without any confirmation.
188 """,
197 """,
189 """Don't prompt the user when exiting. This will terminate the kernel
198 """Don't prompt the user when exiting. This will terminate the kernel
190 if it is owned by the frontend, and leave it alive if it is external.
199 if it is owned by the frontend, and leave it alive if it is external.
191 """
200 """
192 ))
201 ))
193 # the flags that are specific to the frontend
202 # the flags that are specific to the frontend
194 # these must be scrubbed before being passed to the kernel,
203 # these must be scrubbed before being passed to the kernel,
195 # or it will raise an error on unrecognized flags
204 # or it will raise an error on unrecognized flags
196 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
205 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
197 'confirm-exit', 'no-confirm-exit']
206 'confirm-exit', 'no-confirm-exit']
198
207
199 aliases = dict(ipkernel_aliases)
208 aliases = dict(ipkernel_aliases)
200
209
201 aliases.update(dict(
210 aliases.update(dict(
202 hb = 'IPythonQtConsoleApp.hb_port',
211 hb = 'IPythonQtConsoleApp.hb_port',
203 shell = 'IPythonQtConsoleApp.shell_port',
212 shell = 'IPythonQtConsoleApp.shell_port',
204 iopub = 'IPythonQtConsoleApp.iopub_port',
213 iopub = 'IPythonQtConsoleApp.iopub_port',
205 stdin = 'IPythonQtConsoleApp.stdin_port',
214 stdin = 'IPythonQtConsoleApp.stdin_port',
206 ip = 'IPythonQtConsoleApp.ip',
215 ip = 'IPythonQtConsoleApp.ip',
207
216
208 plain = 'IPythonQtConsoleApp.plain',
217 plain = 'IPythonQtConsoleApp.plain',
209 pure = 'IPythonQtConsoleApp.pure',
218 pure = 'IPythonQtConsoleApp.pure',
210 gui_completion = 'ConsoleWidget.gui_completion',
219 gui_completion = 'ConsoleWidget.gui_completion',
211 style = 'IPythonWidget.syntax_style',
220 style = 'IPythonWidget.syntax_style',
212 stylesheet = 'IPythonQtConsoleApp.stylesheet',
221 stylesheet = 'IPythonQtConsoleApp.stylesheet',
213 colors = 'ZMQInteractiveShell.colors',
222 colors = 'ZMQInteractiveShell.colors',
214
223
215 editor = 'IPythonWidget.editor',
224 editor = 'IPythonWidget.editor',
216 ))
225 ))
217
226
218 #-----------------------------------------------------------------------------
227 #-----------------------------------------------------------------------------
219 # IPythonQtConsole
228 # IPythonQtConsole
220 #-----------------------------------------------------------------------------
229 #-----------------------------------------------------------------------------
221
230
222 qt_examples = """
223 ipython qtconsole # start the qtconsole
224 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
225 """
226
231
227 class IPythonQtConsoleApp(BaseIPythonApplication):
232 class IPythonQtConsoleApp(BaseIPythonApplication):
228 name = 'ipython-qtconsole'
233 name = 'ipython-qtconsole'
229 default_config_file_name='ipython_config.py'
234 default_config_file_name='ipython_config.py'
230
235
231 description = """
236 description = """
232 The IPython QtConsole.
237 The IPython QtConsole.
233
238
234 This launches a Console-style application using Qt. It is not a full
239 This launches a Console-style application using Qt. It is not a full
235 console, in that launched terminal subprocesses will not.
240 console, in that launched terminal subprocesses will not.
236
241
237 The QtConsole supports various extra features beyond the
242 The QtConsole supports various extra features beyond the
238
243
239 """
244 """
240 examples = qt_examples
245 examples = _examples
241
246
242 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
247 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
243 flags = Dict(flags)
248 flags = Dict(flags)
244 aliases = Dict(aliases)
249 aliases = Dict(aliases)
245
250
246 kernel_argv = List(Unicode)
251 kernel_argv = List(Unicode)
247
252
248 # connection info:
253 # connection info:
249 ip = Unicode(LOCALHOST, config=True,
254 ip = Unicode(LOCALHOST, config=True,
250 help="""Set the kernel\'s IP address [default localhost].
255 help="""Set the kernel\'s IP address [default localhost].
251 If the IP address is something other than localhost, then
256 If the IP address is something other than localhost, then
252 Consoles on other machines will be able to connect
257 Consoles on other machines will be able to connect
253 to the Kernel, so be careful!"""
258 to the Kernel, so be careful!"""
254 )
259 )
255 hb_port = Int(0, config=True,
260 hb_port = Int(0, config=True,
256 help="set the heartbeat port [default: random]")
261 help="set the heartbeat port [default: random]")
257 shell_port = Int(0, config=True,
262 shell_port = Int(0, config=True,
258 help="set the shell (XREP) port [default: random]")
263 help="set the shell (XREP) port [default: random]")
259 iopub_port = Int(0, config=True,
264 iopub_port = Int(0, config=True,
260 help="set the iopub (PUB) port [default: random]")
265 help="set the iopub (PUB) port [default: random]")
261 stdin_port = Int(0, config=True,
266 stdin_port = Int(0, config=True,
262 help="set the stdin (XREQ) port [default: random]")
267 help="set the stdin (XREQ) port [default: random]")
263
268
264 existing = CBool(False, config=True,
269 existing = CBool(False, config=True,
265 help="Whether to connect to an already running Kernel.")
270 help="Whether to connect to an already running Kernel.")
266
271
267 stylesheet = Unicode('', config=True,
272 stylesheet = Unicode('', config=True,
268 help="path to a custom CSS stylesheet")
273 help="path to a custom CSS stylesheet")
269
274
270 pure = CBool(False, config=True,
275 pure = CBool(False, config=True,
271 help="Use a pure Python kernel instead of an IPython kernel.")
276 help="Use a pure Python kernel instead of an IPython kernel.")
272 plain = CBool(False, config=True,
277 plain = CBool(False, config=True,
273 help="Use a plaintext widget instead of rich text (plain can't print/save).")
278 help="Use a plaintext widget instead of rich text (plain can't print/save).")
274
279
275 def _pure_changed(self, name, old, new):
280 def _pure_changed(self, name, old, new):
276 kind = 'plain' if self.plain else 'rich'
281 kind = 'plain' if self.plain else 'rich'
277 self.config.ConsoleWidget.kind = kind
282 self.config.ConsoleWidget.kind = kind
278 if self.pure:
283 if self.pure:
279 self.widget_factory = FrontendWidget
284 self.widget_factory = FrontendWidget
280 elif self.plain:
285 elif self.plain:
281 self.widget_factory = IPythonWidget
286 self.widget_factory = IPythonWidget
282 else:
287 else:
283 self.widget_factory = RichIPythonWidget
288 self.widget_factory = RichIPythonWidget
284
289
285 _plain_changed = _pure_changed
290 _plain_changed = _pure_changed
286
291
287 confirm_exit = CBool(True, config=True,
292 confirm_exit = CBool(True, config=True,
288 help="""
293 help="""
289 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
294 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
290 to force a direct exit without any confirmation.""",
295 to force a direct exit without any confirmation.""",
291 )
296 )
292
297
293 # the factory for creating a widget
298 # the factory for creating a widget
294 widget_factory = Any(RichIPythonWidget)
299 widget_factory = Any(RichIPythonWidget)
295
300
296 def parse_command_line(self, argv=None):
301 def parse_command_line(self, argv=None):
297 super(IPythonQtConsoleApp, self).parse_command_line(argv)
302 super(IPythonQtConsoleApp, self).parse_command_line(argv)
298 if argv is None:
303 if argv is None:
299 argv = sys.argv[1:]
304 argv = sys.argv[1:]
300
305
301 self.kernel_argv = list(argv) # copy
306 self.kernel_argv = list(argv) # copy
302 # kernel should inherit default config file from frontend
307 # kernel should inherit default config file from frontend
303 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
308 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
304 # scrub frontend-specific flags
309 # scrub frontend-specific flags
305 for a in argv:
310 for a in argv:
306 if a.startswith('-') and a.lstrip('-') in qt_flags:
311 if a.startswith('-') and a.lstrip('-') in qt_flags:
307 self.kernel_argv.remove(a)
312 self.kernel_argv.remove(a)
308
313
309 def init_kernel_manager(self):
314 def init_kernel_manager(self):
310 # Don't let Qt or ZMQ swallow KeyboardInterupts.
315 # Don't let Qt or ZMQ swallow KeyboardInterupts.
311 signal.signal(signal.SIGINT, signal.SIG_DFL)
316 signal.signal(signal.SIGINT, signal.SIG_DFL)
312
317
313 # Create a KernelManager and start a kernel.
318 # Create a KernelManager and start a kernel.
314 self.kernel_manager = QtKernelManager(
319 self.kernel_manager = QtKernelManager(
315 shell_address=(self.ip, self.shell_port),
320 shell_address=(self.ip, self.shell_port),
316 sub_address=(self.ip, self.iopub_port),
321 sub_address=(self.ip, self.iopub_port),
317 stdin_address=(self.ip, self.stdin_port),
322 stdin_address=(self.ip, self.stdin_port),
318 hb_address=(self.ip, self.hb_port),
323 hb_address=(self.ip, self.hb_port),
319 config=self.config
324 config=self.config
320 )
325 )
321 # start the kernel
326 # start the kernel
322 if not self.existing:
327 if not self.existing:
323 kwargs = dict(ip=self.ip, ipython=not self.pure)
328 kwargs = dict(ip=self.ip, ipython=not self.pure)
324 kwargs['extra_arguments'] = self.kernel_argv
329 kwargs['extra_arguments'] = self.kernel_argv
325 self.kernel_manager.start_kernel(**kwargs)
330 self.kernel_manager.start_kernel(**kwargs)
326 self.kernel_manager.start_channels()
331 self.kernel_manager.start_channels()
327
332
328
333
329 def init_qt_elements(self):
334 def init_qt_elements(self):
330 # Create the widget.
335 # Create the widget.
331 self.app = QtGui.QApplication([])
336 self.app = QtGui.QApplication([])
332 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
337 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
333 self.widget = self.widget_factory(config=self.config,
338 self.widget = self.widget_factory(config=self.config,
334 local_kernel=local_kernel)
339 local_kernel=local_kernel)
335 self.widget.kernel_manager = self.kernel_manager
340 self.widget.kernel_manager = self.kernel_manager
336 self.window = MainWindow(self.app, self.widget, self.existing,
341 self.window = MainWindow(self.app, self.widget, self.existing,
337 may_close=local_kernel,
342 may_close=local_kernel,
338 confirm_exit=self.confirm_exit)
343 confirm_exit=self.confirm_exit)
339 self.window.setWindowTitle('Python' if self.pure else 'IPython')
344 self.window.setWindowTitle('Python' if self.pure else 'IPython')
340
345
341 def init_colors(self):
346 def init_colors(self):
342 """Configure the coloring of the widget"""
347 """Configure the coloring of the widget"""
343 # Note: This will be dramatically simplified when colors
348 # Note: This will be dramatically simplified when colors
344 # are removed from the backend.
349 # are removed from the backend.
345
350
346 if self.pure:
351 if self.pure:
347 # only IPythonWidget supports styling
352 # only IPythonWidget supports styling
348 return
353 return
349
354
350 # parse the colors arg down to current known labels
355 # parse the colors arg down to current known labels
351 try:
356 try:
352 colors = self.config.ZMQInteractiveShell.colors
357 colors = self.config.ZMQInteractiveShell.colors
353 except AttributeError:
358 except AttributeError:
354 colors = None
359 colors = None
355 try:
360 try:
356 style = self.config.IPythonWidget.colors
361 style = self.config.IPythonWidget.colors
357 except AttributeError:
362 except AttributeError:
358 style = None
363 style = None
359
364
360 # find the value for colors:
365 # find the value for colors:
361 if colors:
366 if colors:
362 colors=colors.lower()
367 colors=colors.lower()
363 if colors in ('lightbg', 'light'):
368 if colors in ('lightbg', 'light'):
364 colors='lightbg'
369 colors='lightbg'
365 elif colors in ('dark', 'linux'):
370 elif colors in ('dark', 'linux'):
366 colors='linux'
371 colors='linux'
367 else:
372 else:
368 colors='nocolor'
373 colors='nocolor'
369 elif style:
374 elif style:
370 if style=='bw':
375 if style=='bw':
371 colors='nocolor'
376 colors='nocolor'
372 elif styles.dark_style(style):
377 elif styles.dark_style(style):
373 colors='linux'
378 colors='linux'
374 else:
379 else:
375 colors='lightbg'
380 colors='lightbg'
376 else:
381 else:
377 colors=None
382 colors=None
378
383
379 # Configure the style.
384 # Configure the style.
380 widget = self.widget
385 widget = self.widget
381 if style:
386 if style:
382 widget.style_sheet = styles.sheet_from_template(style, colors)
387 widget.style_sheet = styles.sheet_from_template(style, colors)
383 widget.syntax_style = style
388 widget.syntax_style = style
384 widget._syntax_style_changed()
389 widget._syntax_style_changed()
385 widget._style_sheet_changed()
390 widget._style_sheet_changed()
386 elif colors:
391 elif colors:
387 # use a default style
392 # use a default style
388 widget.set_default_style(colors=colors)
393 widget.set_default_style(colors=colors)
389 else:
394 else:
390 # this is redundant for now, but allows the widget's
395 # this is redundant for now, but allows the widget's
391 # defaults to change
396 # defaults to change
392 widget.set_default_style()
397 widget.set_default_style()
393
398
394 if self.stylesheet:
399 if self.stylesheet:
395 # we got an expicit stylesheet
400 # we got an expicit stylesheet
396 if os.path.isfile(self.stylesheet):
401 if os.path.isfile(self.stylesheet):
397 with open(self.stylesheet) as f:
402 with open(self.stylesheet) as f:
398 sheet = f.read()
403 sheet = f.read()
399 widget.style_sheet = sheet
404 widget.style_sheet = sheet
400 widget._style_sheet_changed()
405 widget._style_sheet_changed()
401 else:
406 else:
402 raise IOError("Stylesheet %r not found."%self.stylesheet)
407 raise IOError("Stylesheet %r not found."%self.stylesheet)
403
408
404 def initialize(self, argv=None):
409 def initialize(self, argv=None):
405 super(IPythonQtConsoleApp, self).initialize(argv)
410 super(IPythonQtConsoleApp, self).initialize(argv)
406 self.init_kernel_manager()
411 self.init_kernel_manager()
407 self.init_qt_elements()
412 self.init_qt_elements()
408 self.init_colors()
413 self.init_colors()
409
414
410 def start(self):
415 def start(self):
411
416
412 # draw the window
417 # draw the window
413 self.window.show()
418 self.window.show()
414
419
415 # Start the application main loop.
420 # Start the application main loop.
416 self.app.exec_()
421 self.app.exec_()
417
422
418 #-----------------------------------------------------------------------------
423 #-----------------------------------------------------------------------------
419 # Main entry point
424 # Main entry point
420 #-----------------------------------------------------------------------------
425 #-----------------------------------------------------------------------------
421
426
422 def main():
427 def main():
423 app = IPythonQtConsoleApp()
428 app = IPythonQtConsoleApp()
424 app.initialize()
429 app.initialize()
425 app.start()
430 app.start()
426
431
427
432
428 if __name__ == '__main__':
433 if __name__ == '__main__':
429 main()
434 main()
@@ -1,375 +1,375 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 import warn
48 from IPython.utils import warn
49 from IPython.utils.path import get_ipython_dir, check_for_old_config
49 from IPython.utils.path import get_ipython_dir, check_for_old_config
50 from IPython.utils.traitlets import (
50 from IPython.utils.traitlets import (
51 Bool, Dict, CaselessStrEnum
51 Bool, Dict, CaselessStrEnum
52 )
52 )
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Globals, utilities and helpers
55 # Globals, utilities and helpers
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 #: The default config file name for this application.
58 #: The default config file name for this application.
59 default_config_file_name = u'ipython_config.py'
59 default_config_file_name = u'ipython_config.py'
60
60
61 _examples = """
62 ipython --pylab # start in pylab mode
63 ipython --pylab=qt # start in pylab mode with the qt4 backend
64 ipython --log_level=DEBUG # set logging to DEBUG
65 ipython --profile=foo # start with profile foo
66 ipython qtconsole # start the qtconsole GUI application
67 ipython profile -h # show the help string for the profile subcmd
68 ipython qtconsole -h # show the help string for the qtconsole subcmd
69 """
61
70
62 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
63 # Crash handler for this application
72 # Crash handler for this application
64 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
65
74
66 class IPAppCrashHandler(CrashHandler):
75 class IPAppCrashHandler(CrashHandler):
67 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
76 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
68
77
69 def __init__(self, app):
78 def __init__(self, app):
70 contact_name = release.authors['Fernando'][0]
79 contact_name = release.authors['Fernando'][0]
71 contact_email = release.authors['Fernando'][1]
80 contact_email = release.authors['Fernando'][1]
72 bug_tracker = 'http://github.com/ipython/ipython/issues'
81 bug_tracker = 'http://github.com/ipython/ipython/issues'
73 super(IPAppCrashHandler,self).__init__(
82 super(IPAppCrashHandler,self).__init__(
74 app, contact_name, contact_email, bug_tracker
83 app, contact_name, contact_email, bug_tracker
75 )
84 )
76
85
77 def make_report(self,traceback):
86 def make_report(self,traceback):
78 """Return a string containing a crash report."""
87 """Return a string containing a crash report."""
79
88
80 sec_sep = self.section_sep
89 sec_sep = self.section_sep
81 # Start with parent report
90 # Start with parent report
82 report = [super(IPAppCrashHandler, self).make_report(traceback)]
91 report = [super(IPAppCrashHandler, self).make_report(traceback)]
83 # Add interactive-specific info we may have
92 # Add interactive-specific info we may have
84 rpt_add = report.append
93 rpt_add = report.append
85 try:
94 try:
86 rpt_add(sec_sep+"History of session input:")
95 rpt_add(sec_sep+"History of session input:")
87 for line in self.app.shell.user_ns['_ih']:
96 for line in self.app.shell.user_ns['_ih']:
88 rpt_add(line)
97 rpt_add(line)
89 rpt_add('\n*** Last line of input (may not be in above history):\n')
98 rpt_add('\n*** Last line of input (may not be in above history):\n')
90 rpt_add(self.app.shell._last_input_line+'\n')
99 rpt_add(self.app.shell._last_input_line+'\n')
91 except:
100 except:
92 pass
101 pass
93
102
94 return ''.join(report)
103 return ''.join(report)
95
104
96 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
97 # Aliases and Flags
106 # Aliases and Flags
98 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
99 flags = dict(base_flags)
108 flags = dict(base_flags)
100 flags.update(shell_flags)
109 flags.update(shell_flags)
101 addflag = lambda *args: flags.update(boolean_flag(*args))
110 addflag = lambda *args: flags.update(boolean_flag(*args))
102 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
111 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
103 'Turn on auto editing of files with syntax errors.',
112 'Turn on auto editing of files with syntax errors.',
104 'Turn off auto editing of files with syntax errors.'
113 'Turn off auto editing of files with syntax errors.'
105 )
114 )
106 addflag('banner', 'TerminalIPythonApp.display_banner',
115 addflag('banner', 'TerminalIPythonApp.display_banner',
107 "Display a banner upon starting IPython.",
116 "Display a banner upon starting IPython.",
108 "Don't display a banner upon starting IPython."
117 "Don't display a banner upon starting IPython."
109 )
118 )
110 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
119 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
111 """Set to confirm when you try to exit IPython with an EOF (Control-D
120 """Set to confirm when you try to exit IPython with an EOF (Control-D
112 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
121 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
113 you can force a direct exit without any confirmation.""",
122 you can force a direct exit without any confirmation.""",
114 "Don't prompt the user when exiting."
123 "Don't prompt the user when exiting."
115 )
124 )
116 addflag('term-title', 'TerminalInteractiveShell.term_title',
125 addflag('term-title', 'TerminalInteractiveShell.term_title',
117 "Enable auto setting the terminal title.",
126 "Enable auto setting the terminal title.",
118 "Disable auto setting the terminal title."
127 "Disable auto setting the terminal title."
119 )
128 )
120 classic_config = Config()
129 classic_config = Config()
121 classic_config.InteractiveShell.cache_size = 0
130 classic_config.InteractiveShell.cache_size = 0
122 classic_config.PlainTextFormatter.pprint = False
131 classic_config.PlainTextFormatter.pprint = False
123 classic_config.InteractiveShell.prompt_in1 = '>>> '
132 classic_config.InteractiveShell.prompt_in1 = '>>> '
124 classic_config.InteractiveShell.prompt_in2 = '... '
133 classic_config.InteractiveShell.prompt_in2 = '... '
125 classic_config.InteractiveShell.prompt_out = ''
134 classic_config.InteractiveShell.prompt_out = ''
126 classic_config.InteractiveShell.separate_in = ''
135 classic_config.InteractiveShell.separate_in = ''
127 classic_config.InteractiveShell.separate_out = ''
136 classic_config.InteractiveShell.separate_out = ''
128 classic_config.InteractiveShell.separate_out2 = ''
137 classic_config.InteractiveShell.separate_out2 = ''
129 classic_config.InteractiveShell.colors = 'NoColor'
138 classic_config.InteractiveShell.colors = 'NoColor'
130 classic_config.InteractiveShell.xmode = 'Plain'
139 classic_config.InteractiveShell.xmode = 'Plain'
131
140
132 flags['classic']=(
141 flags['classic']=(
133 classic_config,
142 classic_config,
134 "Gives IPython a similar feel to the classic Python prompt."
143 "Gives IPython a similar feel to the classic Python prompt."
135 )
144 )
136 # # log doesn't make so much sense this way anymore
145 # # log doesn't make so much sense this way anymore
137 # paa('--log','-l',
146 # paa('--log','-l',
138 # action='store_true', dest='InteractiveShell.logstart',
147 # action='store_true', dest='InteractiveShell.logstart',
139 # help="Start logging to the default log file (./ipython_log.py).")
148 # help="Start logging to the default log file (./ipython_log.py).")
140 #
149 #
141 # # quick is harder to implement
150 # # quick is harder to implement
142 flags['quick']=(
151 flags['quick']=(
143 {'TerminalIPythonApp' : {'quick' : True}},
152 {'TerminalIPythonApp' : {'quick' : True}},
144 "Enable quick startup with no config files."
153 "Enable quick startup with no config files."
145 )
154 )
146
155
147 flags['i'] = (
156 flags['i'] = (
148 {'TerminalIPythonApp' : {'force_interact' : True}},
157 {'TerminalIPythonApp' : {'force_interact' : True}},
149 """also works as '-i'
158 """also works as '-i'
150 If running code from the command line, become interactive afterwards."""
159 If running code from the command line, become interactive afterwards."""
151 )
160 )
152 flags['pylab'] = (
161 flags['pylab'] = (
153 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
162 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
154 """Pre-load matplotlib and numpy for interactive use with
163 """Pre-load matplotlib and numpy for interactive use with
155 the default matplotlib backend."""
164 the default matplotlib backend."""
156 )
165 )
157
166
158 aliases = dict(base_aliases)
167 aliases = dict(base_aliases)
159 aliases.update(shell_aliases)
168 aliases.update(shell_aliases)
160
169
161 # it's possible we don't want short aliases for *all* of these:
170 # it's possible we don't want short aliases for *all* of these:
162 aliases.update(dict(
171 aliases.update(dict(
163 gui='TerminalIPythonApp.gui',
172 gui='TerminalIPythonApp.gui',
164 pylab='TerminalIPythonApp.pylab',
173 pylab='TerminalIPythonApp.pylab',
165 ))
174 ))
166
175
167 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
168 # Main classes and functions
177 # Main classes and functions
169 #-----------------------------------------------------------------------------
178 #-----------------------------------------------------------------------------
170
179
171 examples = """
172 ipython --pylab # start in pylab mode
173 ipython --pylab=qt # start in pylab mode with the qt4 backend
174 ipython --log_level=DEBUG # set logging to DEBUG
175 ipython --profile=foo # start with profile foo
176 ipython qtconsole # start the qtconsole GUI application
177 ipython profile -h # show the help string for the profile subcmd
178 ipython qtconsole -h # show the help string for the qtconsole subcmd
179 """
180
180
181 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
181 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
182 name = u'ipython'
182 name = u'ipython'
183 description = usage.cl_usage
183 description = usage.cl_usage
184 default_config_file_name = default_config_file_name
184 default_config_file_name = default_config_file_name
185 crash_handler_class = IPAppCrashHandler
185 crash_handler_class = IPAppCrashHandler
186 examples = examples
186 examples = _examples
187
187
188 flags = Dict(flags)
188 flags = Dict(flags)
189 aliases = Dict(aliases)
189 aliases = Dict(aliases)
190 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
190 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
191 subcommands = Dict(dict(
191 subcommands = Dict(dict(
192 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
192 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
193 """Launch the IPython Qt Console."""
193 """Launch the IPython Qt Console."""
194 ),
194 ),
195 profile = ("IPython.core.profileapp.ProfileApp",
195 profile = ("IPython.core.profileapp.ProfileApp",
196 "Create and manage IPython profiles.")
196 "Create and manage IPython profiles.")
197 ))
197 ))
198
198
199 # *do* autocreate requested profile, but don't create the config file.
199 # *do* autocreate requested profile, but don't create the config file.
200 auto_create=Bool(True)
200 auto_create=Bool(True)
201 # configurables
201 # configurables
202 ignore_old_config=Bool(False, config=True,
202 ignore_old_config=Bool(False, config=True,
203 help="Suppress warning messages about legacy config files"
203 help="Suppress warning messages about legacy config files"
204 )
204 )
205 quick = Bool(False, config=True,
205 quick = Bool(False, config=True,
206 help="""Start IPython quickly by skipping the loading of config files."""
206 help="""Start IPython quickly by skipping the loading of config files."""
207 )
207 )
208 def _quick_changed(self, name, old, new):
208 def _quick_changed(self, name, old, new):
209 if new:
209 if new:
210 self.load_config_file = lambda *a, **kw: None
210 self.load_config_file = lambda *a, **kw: None
211 self.ignore_old_config=True
211 self.ignore_old_config=True
212
212
213 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
213 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
214 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
214 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
215 )
215 )
216 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
216 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
217 config=True,
217 config=True,
218 help="""Pre-load matplotlib and numpy for interactive use,
218 help="""Pre-load matplotlib and numpy for interactive use,
219 selecting a particular matplotlib backend and loop integration.
219 selecting a particular matplotlib backend and loop integration.
220 """
220 """
221 )
221 )
222 display_banner = Bool(True, config=True,
222 display_banner = Bool(True, config=True,
223 help="Whether to display a banner upon starting IPython."
223 help="Whether to display a banner upon starting IPython."
224 )
224 )
225
225
226 # if there is code of files to run from the cmd line, don't interact
226 # if there is code of files to run from the cmd line, don't interact
227 # unless the --i flag (App.force_interact) is true.
227 # unless the --i flag (App.force_interact) is true.
228 force_interact = Bool(False, config=True,
228 force_interact = Bool(False, config=True,
229 help="""If a command or file is given via the command-line,
229 help="""If a command or file is given via the command-line,
230 e.g. 'ipython foo.py"""
230 e.g. 'ipython foo.py"""
231 )
231 )
232 def _force_interact_changed(self, name, old, new):
232 def _force_interact_changed(self, name, old, new):
233 if new:
233 if new:
234 self.interact = True
234 self.interact = True
235
235
236 def _file_to_run_changed(self, name, old, new):
236 def _file_to_run_changed(self, name, old, new):
237 if new and not self.force_interact:
237 if new and not self.force_interact:
238 self.interact = False
238 self.interact = False
239 _code_to_run_changed = _file_to_run_changed
239 _code_to_run_changed = _file_to_run_changed
240
240
241 # internal, not-configurable
241 # internal, not-configurable
242 interact=Bool(True)
242 interact=Bool(True)
243
243
244
244
245 def parse_command_line(self, argv=None):
245 def parse_command_line(self, argv=None):
246 """override to allow old '-pylab' flag with deprecation warning"""
246 """override to allow old '-pylab' flag with deprecation warning"""
247 argv = sys.argv[1:] if argv is None else argv
247 argv = sys.argv[1:] if argv is None else argv
248
248
249 try:
249 try:
250 idx = argv.index('-pylab')
250 idx = argv.index('-pylab')
251 except ValueError:
251 except ValueError:
252 # `-pylab` not given, proceed as normal
252 # `-pylab` not given, proceed as normal
253 pass
253 pass
254 else:
254 else:
255 # deprecated `-pylab` given,
255 # deprecated `-pylab` given,
256 # warn and transform into current syntax
256 # warn and transform into current syntax
257 argv = list(argv) # copy, don't clobber
257 argv = list(argv) # copy, don't clobber
258 warn.warn("`-pylab` flag has been deprecated.\n"
258 warn.warn("`-pylab` flag has been deprecated.\n"
259 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
259 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
260 sub = '--pylab'
260 sub = '--pylab'
261 if len(argv) > idx+1:
261 if len(argv) > idx+1:
262 # check for gui arg, as in '-pylab qt'
262 # check for gui arg, as in '-pylab qt'
263 gui = argv[idx+1]
263 gui = argv[idx+1]
264 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
264 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
265 sub = '--pylab='+gui
265 sub = '--pylab='+gui
266 argv.pop(idx+1)
266 argv.pop(idx+1)
267 argv[idx] = sub
267 argv[idx] = sub
268
268
269 return super(TerminalIPythonApp, self).parse_command_line(argv)
269 return super(TerminalIPythonApp, self).parse_command_line(argv)
270
270
271 def initialize(self, argv=None):
271 def initialize(self, argv=None):
272 """Do actions after construct, but before starting the app."""
272 """Do actions after construct, but before starting the app."""
273 super(TerminalIPythonApp, self).initialize(argv)
273 super(TerminalIPythonApp, self).initialize(argv)
274 if self.subapp is not None:
274 if self.subapp is not None:
275 # don't bother initializing further, starting subapp
275 # don't bother initializing further, starting subapp
276 return
276 return
277 if not self.ignore_old_config:
277 if not self.ignore_old_config:
278 check_for_old_config(self.ipython_dir)
278 check_for_old_config(self.ipython_dir)
279 # print self.extra_args
279 # print self.extra_args
280 if self.extra_args:
280 if self.extra_args:
281 self.file_to_run = self.extra_args[0]
281 self.file_to_run = self.extra_args[0]
282 # create the shell
282 # create the shell
283 self.init_shell()
283 self.init_shell()
284 # and draw the banner
284 # and draw the banner
285 self.init_banner()
285 self.init_banner()
286 # Now a variety of things that happen after the banner is printed.
286 # Now a variety of things that happen after the banner is printed.
287 self.init_gui_pylab()
287 self.init_gui_pylab()
288 self.init_extensions()
288 self.init_extensions()
289 self.init_code()
289 self.init_code()
290
290
291 def init_shell(self):
291 def init_shell(self):
292 """initialize the InteractiveShell instance"""
292 """initialize the InteractiveShell instance"""
293 # I am a little hesitant to put these into InteractiveShell itself.
293 # I am a little hesitant to put these into InteractiveShell itself.
294 # But that might be the place for them
294 # But that might be the place for them
295 sys.path.insert(0, '')
295 sys.path.insert(0, '')
296
296
297 # Create an InteractiveShell instance.
297 # Create an InteractiveShell instance.
298 # shell.display_banner should always be False for the terminal
298 # shell.display_banner should always be False for the terminal
299 # based app, because we call shell.show_banner() by hand below
299 # based app, because we call shell.show_banner() by hand below
300 # so the banner shows *before* all extension loading stuff.
300 # so the banner shows *before* all extension loading stuff.
301 self.shell = TerminalInteractiveShell.instance(config=self.config,
301 self.shell = TerminalInteractiveShell.instance(config=self.config,
302 display_banner=False, profile_dir=self.profile_dir,
302 display_banner=False, profile_dir=self.profile_dir,
303 ipython_dir=self.ipython_dir)
303 ipython_dir=self.ipython_dir)
304
304
305 def init_banner(self):
305 def init_banner(self):
306 """optionally display the banner"""
306 """optionally display the banner"""
307 if self.display_banner and self.interact:
307 if self.display_banner and self.interact:
308 self.shell.show_banner()
308 self.shell.show_banner()
309 # Make sure there is a space below the banner.
309 # Make sure there is a space below the banner.
310 if self.log_level <= logging.INFO: print
310 if self.log_level <= logging.INFO: print
311
311
312
312
313 def init_gui_pylab(self):
313 def init_gui_pylab(self):
314 """Enable GUI event loop integration, taking pylab into account."""
314 """Enable GUI event loop integration, taking pylab into account."""
315 gui = self.gui
315 gui = self.gui
316
316
317 # Using `pylab` will also require gui activation, though which toolkit
317 # Using `pylab` will also require gui activation, though which toolkit
318 # to use may be chosen automatically based on mpl configuration.
318 # to use may be chosen automatically based on mpl configuration.
319 if self.pylab:
319 if self.pylab:
320 activate = self.shell.enable_pylab
320 activate = self.shell.enable_pylab
321 if self.pylab == 'auto':
321 if self.pylab == 'auto':
322 gui = None
322 gui = None
323 else:
323 else:
324 gui = self.pylab
324 gui = self.pylab
325 else:
325 else:
326 # Enable only GUI integration, no pylab
326 # Enable only GUI integration, no pylab
327 activate = inputhook.enable_gui
327 activate = inputhook.enable_gui
328
328
329 if gui or self.pylab:
329 if gui or self.pylab:
330 try:
330 try:
331 self.log.info("Enabling GUI event loop integration, "
331 self.log.info("Enabling GUI event loop integration, "
332 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
332 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
333 activate(gui)
333 activate(gui)
334 except:
334 except:
335 self.log.warn("Error in enabling GUI event loop integration:")
335 self.log.warn("Error in enabling GUI event loop integration:")
336 self.shell.showtraceback()
336 self.shell.showtraceback()
337
337
338 def start(self):
338 def start(self):
339 if self.subapp is not None:
339 if self.subapp is not None:
340 return self.subapp.start()
340 return self.subapp.start()
341 # perform any prexec steps:
341 # perform any prexec steps:
342 if self.interact:
342 if self.interact:
343 self.log.debug("Starting IPython's mainloop...")
343 self.log.debug("Starting IPython's mainloop...")
344 self.shell.mainloop()
344 self.shell.mainloop()
345 else:
345 else:
346 self.log.debug("IPython not interactive...")
346 self.log.debug("IPython not interactive...")
347
347
348
348
349 def load_default_config(ipython_dir=None):
349 def load_default_config(ipython_dir=None):
350 """Load the default config file from the default ipython_dir.
350 """Load the default config file from the default ipython_dir.
351
351
352 This is useful for embedded shells.
352 This is useful for embedded shells.
353 """
353 """
354 if ipython_dir is None:
354 if ipython_dir is None:
355 ipython_dir = get_ipython_dir()
355 ipython_dir = get_ipython_dir()
356 profile_dir = os.path.join(ipython_dir, 'profile_default')
356 profile_dir = os.path.join(ipython_dir, 'profile_default')
357 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
357 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
358 try:
358 try:
359 config = cl.load_config()
359 config = cl.load_config()
360 except IOError:
360 except IOError:
361 # no config found
361 # no config found
362 config = Config()
362 config = Config()
363 return config
363 return config
364
364
365
365
366 def launch_new_instance():
366 def launch_new_instance():
367 """Create and run a full blown IPython instance"""
367 """Create and run a full blown IPython instance"""
368 app = TerminalIPythonApp.instance()
368 app = TerminalIPythonApp.instance()
369 app.initialize()
369 app.initialize()
370 app.start()
370 app.start()
371
371
372
372
373 if __name__ == '__main__':
373 if __name__ == '__main__':
374 launch_new_instance()
374 launch_new_instance()
375
375
@@ -1,459 +1,482 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 DottedObjectName)
41 DottedObjectName)
42
42
43 from IPython.parallel.apps.baseapp import (
43 from IPython.parallel.apps.baseapp import (
44 BaseParallelApplication,
44 BaseParallelApplication,
45 PIDFileError,
45 PIDFileError,
46 base_flags, base_aliases
46 base_flags, base_aliases
47 )
47 )
48
48
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Module level variables
51 # Module level variables
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54
54
55 default_config_file_name = u'ipcluster_config.py'
55 default_config_file_name = u'ipcluster_config.py'
56
56
57
57
58 _description = """Start an IPython cluster for parallel computing.
58 _description = """Start an IPython cluster for parallel computing.
59
59
60 An IPython cluster consists of 1 controller and 1 or more engines.
60 An IPython cluster consists of 1 controller and 1 or more engines.
61 This command automates the startup of these processes using a wide
61 This command automates the startup of these processes using a wide
62 range of startup methods (SSH, local processes, PBS, mpiexec,
62 range of startup methods (SSH, local processes, PBS, mpiexec,
63 Windows HPC Server 2008). To start a cluster with 4 engines on your
63 Windows HPC Server 2008). To start a cluster with 4 engines on your
64 local host simply do 'ipcluster start n=4'. For more complex usage
64 local host simply do 'ipcluster start --n=4'. For more complex usage
65 you will typically do 'ipcluster create profile=mycluster', then edit
65 you will typically do 'ipython create mycluster --parallel', then edit
66 configuration files, followed by 'ipcluster start profile=mycluster n=4'.
66 configuration files, followed by 'ipcluster start --profile=mycluster --n=4'.
67 """
68
69 _main_examples = """
70 ipcluster start -h # show the help string for the start subcmd
71 ipcluster stop -h # show the help string for the stop subcmd
72 ipcluster engines -h # show the help string for the engines subcmd
73 """
74
75 _start_examples = """
76 ipython profile create mycluster --parallel # create mycluster profile
77 ipcluster start --profile=mycluster --n=4 # start mycluster with 4 nodes
78 """
79
80 _stop_examples = """
81 ipcluster stop --profile=mycluster # stop a running cluster by profile name
82 """
83
84 _engines_examples = """
85 ipcluster engines --profile=mycluster --n=4 # start 4 engines only
67 """
86 """
68
87
69
88
70 # Exit codes for ipcluster
89 # Exit codes for ipcluster
71
90
72 # This will be the exit code if the ipcluster appears to be running because
91 # This will be the exit code if the ipcluster appears to be running because
73 # a .pid file exists
92 # a .pid file exists
74 ALREADY_STARTED = 10
93 ALREADY_STARTED = 10
75
94
76
95
77 # This will be the exit code if ipcluster stop is run, but there is not .pid
96 # This will be the exit code if ipcluster stop is run, but there is not .pid
78 # file to be found.
97 # file to be found.
79 ALREADY_STOPPED = 11
98 ALREADY_STOPPED = 11
80
99
81 # This will be the exit code if ipcluster engines is run, but there is not .pid
100 # This will be the exit code if ipcluster engines is run, but there is not .pid
82 # file to be found.
101 # file to be found.
83 NO_CLUSTER = 12
102 NO_CLUSTER = 12
84
103
85
104
86 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
87 # Main application
106 # Main application
88 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
89 start_help = """Start an IPython cluster for parallel computing
108 start_help = """Start an IPython cluster for parallel computing
90
109
91 Start an ipython cluster by its profile name or cluster
110 Start an ipython cluster by its profile name or cluster
92 directory. Cluster directories contain configuration, log and
111 directory. Cluster directories contain configuration, log and
93 security related files and are named using the convention
112 security related files and are named using the convention
94 'profile_<name>' and should be creating using the 'start'
113 'profile_<name>' and should be creating using the 'start'
95 subcommand of 'ipcluster'. If your cluster directory is in
114 subcommand of 'ipcluster'. If your cluster directory is in
96 the cwd or the ipython directory, you can simply refer to it
115 the cwd or the ipython directory, you can simply refer to it
97 using its profile name, 'ipcluster start n=4 profile=<profile>`,
116 using its profile name, 'ipcluster start n=4 profile=<profile>`,
98 otherwise use the 'profile_dir' option.
117 otherwise use the 'profile_dir' option.
99 """
118 """
100 stop_help = """Stop a running IPython cluster
119 stop_help = """Stop a running IPython cluster
101
120
102 Stop a running ipython cluster by its profile name or cluster
121 Stop a running ipython cluster by its profile name or cluster
103 directory. Cluster directories are named using the convention
122 directory. Cluster directories are named using the convention
104 'profile_<name>'. If your cluster directory is in
123 'profile_<name>'. If your cluster directory is in
105 the cwd or the ipython directory, you can simply refer to it
124 the cwd or the ipython directory, you can simply refer to it
106 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
125 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
107 use the 'profile_dir' option.
126 use the 'profile_dir' option.
108 """
127 """
109 engines_help = """Start engines connected to an existing IPython cluster
128 engines_help = """Start engines connected to an existing IPython cluster
110
129
111 Start one or more engines to connect to an existing Cluster
130 Start one or more engines to connect to an existing Cluster
112 by profile name or cluster directory.
131 by profile name or cluster directory.
113 Cluster directories contain configuration, log and
132 Cluster directories contain configuration, log and
114 security related files and are named using the convention
133 security related files and are named using the convention
115 'profile_<name>' and should be creating using the 'start'
134 'profile_<name>' and should be creating using the 'start'
116 subcommand of 'ipcluster'. If your cluster directory is in
135 subcommand of 'ipcluster'. If your cluster directory is in
117 the cwd or the ipython directory, you can simply refer to it
136 the cwd or the ipython directory, you can simply refer to it
118 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
137 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
119 otherwise use the 'profile_dir' option.
138 otherwise use the 'profile_dir' option.
120 """
139 """
121 stop_aliases = dict(
140 stop_aliases = dict(
122 signal='IPClusterStop.signal',
141 signal='IPClusterStop.signal',
123 )
142 )
124 stop_aliases.update(base_aliases)
143 stop_aliases.update(base_aliases)
125
144
126 class IPClusterStop(BaseParallelApplication):
145 class IPClusterStop(BaseParallelApplication):
127 name = u'ipcluster'
146 name = u'ipcluster'
128 description = stop_help
147 description = stop_help
148 examples = _stop_examples
129 config_file_name = Unicode(default_config_file_name)
149 config_file_name = Unicode(default_config_file_name)
130
150
131 signal = Int(signal.SIGINT, config=True,
151 signal = Int(signal.SIGINT, config=True,
132 help="signal to use for stopping processes.")
152 help="signal to use for stopping processes.")
133
153
134 aliases = Dict(stop_aliases)
154 aliases = Dict(stop_aliases)
135
155
136 def start(self):
156 def start(self):
137 """Start the app for the stop subcommand."""
157 """Start the app for the stop subcommand."""
138 try:
158 try:
139 pid = self.get_pid_from_file()
159 pid = self.get_pid_from_file()
140 except PIDFileError:
160 except PIDFileError:
141 self.log.critical(
161 self.log.critical(
142 'Could not read pid file, cluster is probably not running.'
162 'Could not read pid file, cluster is probably not running.'
143 )
163 )
144 # Here I exit with a unusual exit status that other processes
164 # Here I exit with a unusual exit status that other processes
145 # can watch for to learn how I existed.
165 # can watch for to learn how I existed.
146 self.remove_pid_file()
166 self.remove_pid_file()
147 self.exit(ALREADY_STOPPED)
167 self.exit(ALREADY_STOPPED)
148
168
149 if not self.check_pid(pid):
169 if not self.check_pid(pid):
150 self.log.critical(
170 self.log.critical(
151 'Cluster [pid=%r] is not running.' % pid
171 'Cluster [pid=%r] is not running.' % pid
152 )
172 )
153 self.remove_pid_file()
173 self.remove_pid_file()
154 # Here I exit with a unusual exit status that other processes
174 # Here I exit with a unusual exit status that other processes
155 # can watch for to learn how I existed.
175 # can watch for to learn how I existed.
156 self.exit(ALREADY_STOPPED)
176 self.exit(ALREADY_STOPPED)
157
177
158 elif os.name=='posix':
178 elif os.name=='posix':
159 sig = self.signal
179 sig = self.signal
160 self.log.info(
180 self.log.info(
161 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
181 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
162 )
182 )
163 try:
183 try:
164 os.kill(pid, sig)
184 os.kill(pid, sig)
165 except OSError:
185 except OSError:
166 self.log.error("Stopping cluster failed, assuming already dead.",
186 self.log.error("Stopping cluster failed, assuming already dead.",
167 exc_info=True)
187 exc_info=True)
168 self.remove_pid_file()
188 self.remove_pid_file()
169 elif os.name=='nt':
189 elif os.name=='nt':
170 try:
190 try:
171 # kill the whole tree
191 # kill the whole tree
172 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
192 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
173 except (CalledProcessError, OSError):
193 except (CalledProcessError, OSError):
174 self.log.error("Stopping cluster failed, assuming already dead.",
194 self.log.error("Stopping cluster failed, assuming already dead.",
175 exc_info=True)
195 exc_info=True)
176 self.remove_pid_file()
196 self.remove_pid_file()
177
197
178 engine_aliases = {}
198 engine_aliases = {}
179 engine_aliases.update(base_aliases)
199 engine_aliases.update(base_aliases)
180 engine_aliases.update(dict(
200 engine_aliases.update(dict(
181 n='IPClusterEngines.n',
201 n='IPClusterEngines.n',
182 engines = 'IPClusterEngines.engine_launcher_class',
202 engines = 'IPClusterEngines.engine_launcher_class',
183 daemonize = 'IPClusterEngines.daemonize',
203 daemonize = 'IPClusterEngines.daemonize',
184 ))
204 ))
185 engine_flags = {}
205 engine_flags = {}
186 engine_flags.update(base_flags)
206 engine_flags.update(base_flags)
187
207
188 engine_flags.update(dict(
208 engine_flags.update(dict(
189 daemonize=(
209 daemonize=(
190 {'IPClusterEngines' : {'daemonize' : True}},
210 {'IPClusterEngines' : {'daemonize' : True}},
191 """run the cluster into the background (not available on Windows)""",
211 """run the cluster into the background (not available on Windows)""",
192 )
212 )
193 ))
213 ))
194 class IPClusterEngines(BaseParallelApplication):
214 class IPClusterEngines(BaseParallelApplication):
195
215
196 name = u'ipcluster'
216 name = u'ipcluster'
197 description = engines_help
217 description = engines_help
218 examples = _engines_examples
198 usage = None
219 usage = None
199 config_file_name = Unicode(default_config_file_name)
220 config_file_name = Unicode(default_config_file_name)
200 default_log_level = logging.INFO
221 default_log_level = logging.INFO
201 classes = List()
222 classes = List()
202 def _classes_default(self):
223 def _classes_default(self):
203 from IPython.parallel.apps import launcher
224 from IPython.parallel.apps import launcher
204 launchers = launcher.all_launchers
225 launchers = launcher.all_launchers
205 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
226 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
206 return [ProfileDir]+eslaunchers
227 return [ProfileDir]+eslaunchers
207
228
208 n = Int(2, config=True,
229 n = Int(2, config=True,
209 help="The number of engines to start.")
230 help="The number of engines to start.")
210
231
211 engine_launcher_class = DottedObjectName('LocalEngineSetLauncher',
232 engine_launcher_class = DottedObjectName('LocalEngineSetLauncher',
212 config=True,
233 config=True,
213 help="The class for launching a set of Engines."
234 help="The class for launching a set of Engines."
214 )
235 )
215 daemonize = Bool(False, config=True,
236 daemonize = Bool(False, config=True,
216 help="""Daemonize the ipcluster program. This implies --log-to-file.
237 help="""Daemonize the ipcluster program. This implies --log-to-file.
217 Not available on Windows.
238 Not available on Windows.
218 """)
239 """)
219
240
220 def _daemonize_changed(self, name, old, new):
241 def _daemonize_changed(self, name, old, new):
221 if new:
242 if new:
222 self.log_to_file = True
243 self.log_to_file = True
223
244
224 aliases = Dict(engine_aliases)
245 aliases = Dict(engine_aliases)
225 flags = Dict(engine_flags)
246 flags = Dict(engine_flags)
226 _stopping = False
247 _stopping = False
227
248
228 def initialize(self, argv=None):
249 def initialize(self, argv=None):
229 super(IPClusterEngines, self).initialize(argv)
250 super(IPClusterEngines, self).initialize(argv)
230 self.init_signal()
251 self.init_signal()
231 self.init_launchers()
252 self.init_launchers()
232
253
233 def init_launchers(self):
254 def init_launchers(self):
234 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
255 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
235 self.engine_launcher.on_stop(lambda r: self.loop.stop())
256 self.engine_launcher.on_stop(lambda r: self.loop.stop())
236
257
237 def init_signal(self):
258 def init_signal(self):
238 # Setup signals
259 # Setup signals
239 signal.signal(signal.SIGINT, self.sigint_handler)
260 signal.signal(signal.SIGINT, self.sigint_handler)
240
261
241 def build_launcher(self, clsname):
262 def build_launcher(self, clsname):
242 """import and instantiate a Launcher based on importstring"""
263 """import and instantiate a Launcher based on importstring"""
243 if '.' not in clsname:
264 if '.' not in clsname:
244 # not a module, presume it's the raw name in apps.launcher
265 # not a module, presume it's the raw name in apps.launcher
245 clsname = 'IPython.parallel.apps.launcher.'+clsname
266 clsname = 'IPython.parallel.apps.launcher.'+clsname
246 # print repr(clsname)
267 # print repr(clsname)
247 klass = import_item(clsname)
268 klass = import_item(clsname)
248
269
249 launcher = klass(
270 launcher = klass(
250 work_dir=self.profile_dir.location, config=self.config, log=self.log
271 work_dir=self.profile_dir.location, config=self.config, log=self.log
251 )
272 )
252 return launcher
273 return launcher
253
274
254 def start_engines(self):
275 def start_engines(self):
255 self.log.info("Starting %i engines"%self.n)
276 self.log.info("Starting %i engines"%self.n)
256 self.engine_launcher.start(
277 self.engine_launcher.start(
257 self.n,
278 self.n,
258 self.profile_dir.location
279 self.profile_dir.location
259 )
280 )
260
281
261 def stop_engines(self):
282 def stop_engines(self):
262 self.log.info("Stopping Engines...")
283 self.log.info("Stopping Engines...")
263 if self.engine_launcher.running:
284 if self.engine_launcher.running:
264 d = self.engine_launcher.stop()
285 d = self.engine_launcher.stop()
265 return d
286 return d
266 else:
287 else:
267 return None
288 return None
268
289
269 def stop_launchers(self, r=None):
290 def stop_launchers(self, r=None):
270 if not self._stopping:
291 if not self._stopping:
271 self._stopping = True
292 self._stopping = True
272 self.log.error("IPython cluster: stopping")
293 self.log.error("IPython cluster: stopping")
273 self.stop_engines()
294 self.stop_engines()
274 # Wait a few seconds to let things shut down.
295 # Wait a few seconds to let things shut down.
275 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
296 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
276 dc.start()
297 dc.start()
277
298
278 def sigint_handler(self, signum, frame):
299 def sigint_handler(self, signum, frame):
279 self.log.debug("SIGINT received, stopping launchers...")
300 self.log.debug("SIGINT received, stopping launchers...")
280 self.stop_launchers()
301 self.stop_launchers()
281
302
282 def start_logging(self):
303 def start_logging(self):
283 # Remove old log files of the controller and engine
304 # Remove old log files of the controller and engine
284 if self.clean_logs:
305 if self.clean_logs:
285 log_dir = self.profile_dir.log_dir
306 log_dir = self.profile_dir.log_dir
286 for f in os.listdir(log_dir):
307 for f in os.listdir(log_dir):
287 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
308 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
288 os.remove(os.path.join(log_dir, f))
309 os.remove(os.path.join(log_dir, f))
289 # This will remove old log files for ipcluster itself
310 # This will remove old log files for ipcluster itself
290 # super(IPBaseParallelApplication, self).start_logging()
311 # super(IPBaseParallelApplication, self).start_logging()
291
312
292 def start(self):
313 def start(self):
293 """Start the app for the engines subcommand."""
314 """Start the app for the engines subcommand."""
294 self.log.info("IPython cluster: started")
315 self.log.info("IPython cluster: started")
295 # First see if the cluster is already running
316 # First see if the cluster is already running
296
317
297 # Now log and daemonize
318 # Now log and daemonize
298 self.log.info(
319 self.log.info(
299 'Starting engines with [daemon=%r]' % self.daemonize
320 'Starting engines with [daemon=%r]' % self.daemonize
300 )
321 )
301 # TODO: Get daemonize working on Windows or as a Windows Server.
322 # TODO: Get daemonize working on Windows or as a Windows Server.
302 if self.daemonize:
323 if self.daemonize:
303 if os.name=='posix':
324 if os.name=='posix':
304 daemonize()
325 daemonize()
305
326
306 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
327 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
307 dc.start()
328 dc.start()
308 # Now write the new pid file AFTER our new forked pid is active.
329 # Now write the new pid file AFTER our new forked pid is active.
309 # self.write_pid_file()
330 # self.write_pid_file()
310 try:
331 try:
311 self.loop.start()
332 self.loop.start()
312 except KeyboardInterrupt:
333 except KeyboardInterrupt:
313 pass
334 pass
314 except zmq.ZMQError as e:
335 except zmq.ZMQError as e:
315 if e.errno == errno.EINTR:
336 if e.errno == errno.EINTR:
316 pass
337 pass
317 else:
338 else:
318 raise
339 raise
319
340
320 start_aliases = {}
341 start_aliases = {}
321 start_aliases.update(engine_aliases)
342 start_aliases.update(engine_aliases)
322 start_aliases.update(dict(
343 start_aliases.update(dict(
323 delay='IPClusterStart.delay',
344 delay='IPClusterStart.delay',
324 controller = 'IPClusterStart.controller_launcher_class',
345 controller = 'IPClusterStart.controller_launcher_class',
325 ))
346 ))
326 start_aliases['clean-logs'] = 'IPClusterStart.clean_logs'
347 start_aliases['clean-logs'] = 'IPClusterStart.clean_logs'
327
348
328 class IPClusterStart(IPClusterEngines):
349 class IPClusterStart(IPClusterEngines):
329
350
330 name = u'ipcluster'
351 name = u'ipcluster'
331 description = start_help
352 description = start_help
353 examples = _start_examples
332 default_log_level = logging.INFO
354 default_log_level = logging.INFO
333 auto_create = Bool(True, config=True,
355 auto_create = Bool(True, config=True,
334 help="whether to create the profile_dir if it doesn't exist")
356 help="whether to create the profile_dir if it doesn't exist")
335 classes = List()
357 classes = List()
336 def _classes_default(self,):
358 def _classes_default(self,):
337 from IPython.parallel.apps import launcher
359 from IPython.parallel.apps import launcher
338 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
360 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
339
361
340 clean_logs = Bool(True, config=True,
362 clean_logs = Bool(True, config=True,
341 help="whether to cleanup old logs before starting")
363 help="whether to cleanup old logs before starting")
342
364
343 delay = CFloat(1., config=True,
365 delay = CFloat(1., config=True,
344 help="delay (in s) between starting the controller and the engines")
366 help="delay (in s) between starting the controller and the engines")
345
367
346 controller_launcher_class = DottedObjectName('LocalControllerLauncher',
368 controller_launcher_class = DottedObjectName('LocalControllerLauncher',
347 config=True,
369 config=True,
348 help="The class for launching a Controller."
370 help="The class for launching a Controller."
349 )
371 )
350 reset = Bool(False, config=True,
372 reset = Bool(False, config=True,
351 help="Whether to reset config files as part of '--create'."
373 help="Whether to reset config files as part of '--create'."
352 )
374 )
353
375
354 # flags = Dict(flags)
376 # flags = Dict(flags)
355 aliases = Dict(start_aliases)
377 aliases = Dict(start_aliases)
356
378
357 def init_launchers(self):
379 def init_launchers(self):
358 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
380 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
359 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
381 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
360 self.controller_launcher.on_stop(self.stop_launchers)
382 self.controller_launcher.on_stop(self.stop_launchers)
361
383
362 def start_controller(self):
384 def start_controller(self):
363 self.controller_launcher.start(
385 self.controller_launcher.start(
364 self.profile_dir.location
386 self.profile_dir.location
365 )
387 )
366
388
367 def stop_controller(self):
389 def stop_controller(self):
368 # self.log.info("In stop_controller")
390 # self.log.info("In stop_controller")
369 if self.controller_launcher and self.controller_launcher.running:
391 if self.controller_launcher and self.controller_launcher.running:
370 return self.controller_launcher.stop()
392 return self.controller_launcher.stop()
371
393
372 def stop_launchers(self, r=None):
394 def stop_launchers(self, r=None):
373 if not self._stopping:
395 if not self._stopping:
374 self.stop_controller()
396 self.stop_controller()
375 super(IPClusterStart, self).stop_launchers()
397 super(IPClusterStart, self).stop_launchers()
376
398
377 def start(self):
399 def start(self):
378 """Start the app for the start subcommand."""
400 """Start the app for the start subcommand."""
379 # First see if the cluster is already running
401 # First see if the cluster is already running
380 try:
402 try:
381 pid = self.get_pid_from_file()
403 pid = self.get_pid_from_file()
382 except PIDFileError:
404 except PIDFileError:
383 pass
405 pass
384 else:
406 else:
385 if self.check_pid(pid):
407 if self.check_pid(pid):
386 self.log.critical(
408 self.log.critical(
387 'Cluster is already running with [pid=%s]. '
409 'Cluster is already running with [pid=%s]. '
388 'use "ipcluster stop" to stop the cluster.' % pid
410 'use "ipcluster stop" to stop the cluster.' % pid
389 )
411 )
390 # Here I exit with a unusual exit status that other processes
412 # Here I exit with a unusual exit status that other processes
391 # can watch for to learn how I existed.
413 # can watch for to learn how I existed.
392 self.exit(ALREADY_STARTED)
414 self.exit(ALREADY_STARTED)
393 else:
415 else:
394 self.remove_pid_file()
416 self.remove_pid_file()
395
417
396
418
397 # Now log and daemonize
419 # Now log and daemonize
398 self.log.info(
420 self.log.info(
399 'Starting ipcluster with [daemon=%r]' % self.daemonize
421 'Starting ipcluster with [daemon=%r]' % self.daemonize
400 )
422 )
401 # TODO: Get daemonize working on Windows or as a Windows Server.
423 # TODO: Get daemonize working on Windows or as a Windows Server.
402 if self.daemonize:
424 if self.daemonize:
403 if os.name=='posix':
425 if os.name=='posix':
404 daemonize()
426 daemonize()
405
427
406 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
428 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
407 dc.start()
429 dc.start()
408 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
430 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
409 dc.start()
431 dc.start()
410 # Now write the new pid file AFTER our new forked pid is active.
432 # Now write the new pid file AFTER our new forked pid is active.
411 self.write_pid_file()
433 self.write_pid_file()
412 try:
434 try:
413 self.loop.start()
435 self.loop.start()
414 except KeyboardInterrupt:
436 except KeyboardInterrupt:
415 pass
437 pass
416 except zmq.ZMQError as e:
438 except zmq.ZMQError as e:
417 if e.errno == errno.EINTR:
439 if e.errno == errno.EINTR:
418 pass
440 pass
419 else:
441 else:
420 raise
442 raise
421 finally:
443 finally:
422 self.remove_pid_file()
444 self.remove_pid_file()
423
445
424 base='IPython.parallel.apps.ipclusterapp.IPCluster'
446 base='IPython.parallel.apps.ipclusterapp.IPCluster'
425
447
426 class IPClusterApp(Application):
448 class IPClusterApp(Application):
427 name = u'ipcluster'
449 name = u'ipcluster'
428 description = _description
450 description = _description
429
451 examples = _main_examples
452
430 subcommands = {
453 subcommands = {
431 'start' : (base+'Start', start_help),
454 'start' : (base+'Start', start_help),
432 'stop' : (base+'Stop', stop_help),
455 'stop' : (base+'Stop', stop_help),
433 'engines' : (base+'Engines', engines_help),
456 'engines' : (base+'Engines', engines_help),
434 }
457 }
435
458
436 # no aliases or flags for parent App
459 # no aliases or flags for parent App
437 aliases = Dict()
460 aliases = Dict()
438 flags = Dict()
461 flags = Dict()
439
462
440 def start(self):
463 def start(self):
441 if self.subapp is None:
464 if self.subapp is None:
442 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
465 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
443 print
466 print
444 self.print_description()
467 self.print_description()
445 self.print_subcommands()
468 self.print_subcommands()
446 self.exit(1)
469 self.exit(1)
447 else:
470 else:
448 return self.subapp.start()
471 return self.subapp.start()
449
472
450 def launch_new_instance():
473 def launch_new_instance():
451 """Create and run the IPython cluster."""
474 """Create and run the IPython cluster."""
452 app = IPClusterApp.instance()
475 app = IPClusterApp.instance()
453 app.initialize()
476 app.initialize()
454 app.start()
477 app.start()
455
478
456
479
457 if __name__ == '__main__':
480 if __name__ == '__main__':
458 launch_new_instance()
481 launch_new_instance()
459
482
@@ -1,420 +1,425 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython controller application.
4 The IPython controller 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 from __future__ import with_statement
24 from __future__ import with_statement
25
25
26 import os
26 import os
27 import socket
27 import socket
28 import stat
28 import stat
29 import sys
29 import sys
30 import uuid
30 import uuid
31
31
32 from multiprocessing import Process
32 from multiprocessing import Process
33
33
34 import zmq
34 import zmq
35 from zmq.devices import ProcessMonitoredQueue
35 from zmq.devices import ProcessMonitoredQueue
36 from zmq.log.handlers import PUBHandler
36 from zmq.log.handlers import PUBHandler
37 from zmq.utils import jsonapi as json
37 from zmq.utils import jsonapi as json
38
38
39 from IPython.config.application import boolean_flag
39 from IPython.config.application import boolean_flag
40 from IPython.core.profiledir import ProfileDir
40 from IPython.core.profiledir import ProfileDir
41
41
42 from IPython.parallel.apps.baseapp import (
42 from IPython.parallel.apps.baseapp import (
43 BaseParallelApplication,
43 BaseParallelApplication,
44 base_aliases,
44 base_aliases,
45 base_flags,
45 base_flags,
46 )
46 )
47 from IPython.utils.importstring import import_item
47 from IPython.utils.importstring import import_item
48 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict
48 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict
49
49
50 # from IPython.parallel.controller.controller import ControllerFactory
50 # from IPython.parallel.controller.controller import ControllerFactory
51 from IPython.zmq.session import Session
51 from IPython.zmq.session import Session
52 from IPython.parallel.controller.heartmonitor import HeartMonitor
52 from IPython.parallel.controller.heartmonitor import HeartMonitor
53 from IPython.parallel.controller.hub import HubFactory
53 from IPython.parallel.controller.hub import HubFactory
54 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
54 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
55 from IPython.parallel.controller.sqlitedb import SQLiteDB
55 from IPython.parallel.controller.sqlitedb import SQLiteDB
56
56
57 from IPython.parallel.util import signal_children, split_url, asbytes
57 from IPython.parallel.util import signal_children, split_url, asbytes
58
58
59 # conditional import of MongoDB backend class
59 # conditional import of MongoDB backend class
60
60
61 try:
61 try:
62 from IPython.parallel.controller.mongodb import MongoDB
62 from IPython.parallel.controller.mongodb import MongoDB
63 except ImportError:
63 except ImportError:
64 maybe_mongo = []
64 maybe_mongo = []
65 else:
65 else:
66 maybe_mongo = [MongoDB]
66 maybe_mongo = [MongoDB]
67
67
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Module level variables
70 # Module level variables
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73
73
74 #: The default config file name for this application
74 #: The default config file name for this application
75 default_config_file_name = u'ipcontroller_config.py'
75 default_config_file_name = u'ipcontroller_config.py'
76
76
77
77
78 _description = """Start the IPython controller for parallel computing.
78 _description = """Start the IPython controller for parallel computing.
79
79
80 The IPython controller provides a gateway between the IPython engines and
80 The IPython controller provides a gateway between the IPython engines and
81 clients. The controller needs to be started before the engines and can be
81 clients. The controller needs to be started before the engines and can be
82 configured using command line options or using a cluster directory. Cluster
82 configured using command line options or using a cluster directory. Cluster
83 directories contain config, log and security files and are usually located in
83 directories contain config, log and security files and are usually located in
84 your ipython directory and named as "profile_name". See the `profile`
84 your ipython directory and named as "profile_name". See the `profile`
85 and `profile_dir` options for details.
85 and `profile_dir` options for details.
86 """
86 """
87
87
88
88 _examples = """
89 ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines
90 ipcontroller --scheme=pure # use the pure zeromq scheduler
91 """
89
92
90
93
91 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
92 # The main application
95 # The main application
93 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
94 flags = {}
97 flags = {}
95 flags.update(base_flags)
98 flags.update(base_flags)
96 flags.update({
99 flags.update({
97 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
100 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
98 'Use threads instead of processes for the schedulers'),
101 'Use threads instead of processes for the schedulers'),
99 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
102 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
100 'use the SQLiteDB backend'),
103 'use the SQLiteDB backend'),
101 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
104 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
102 'use the MongoDB backend'),
105 'use the MongoDB backend'),
103 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
106 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
104 'use the in-memory DictDB backend'),
107 'use the in-memory DictDB backend'),
105 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
108 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
106 'reuse existing json connection files')
109 'reuse existing json connection files')
107 })
110 })
108
111
109 flags.update(boolean_flag('secure', 'IPControllerApp.secure',
112 flags.update(boolean_flag('secure', 'IPControllerApp.secure',
110 "Use HMAC digests for authentication of messages.",
113 "Use HMAC digests for authentication of messages.",
111 "Don't authenticate messages."
114 "Don't authenticate messages."
112 ))
115 ))
113 aliases = dict(
116 aliases = dict(
114 secure = 'IPControllerApp.secure',
117 secure = 'IPControllerApp.secure',
115 ssh = 'IPControllerApp.ssh_server',
118 ssh = 'IPControllerApp.ssh_server',
116 location = 'IPControllerApp.location',
119 location = 'IPControllerApp.location',
117
120
118 ident = 'Session.session',
121 ident = 'Session.session',
119 user = 'Session.username',
122 user = 'Session.username',
120 keyfile = 'Session.keyfile',
123 keyfile = 'Session.keyfile',
121
124
122 url = 'HubFactory.url',
125 url = 'HubFactory.url',
123 ip = 'HubFactory.ip',
126 ip = 'HubFactory.ip',
124 transport = 'HubFactory.transport',
127 transport = 'HubFactory.transport',
125 port = 'HubFactory.regport',
128 port = 'HubFactory.regport',
126
129
127 ping = 'HeartMonitor.period',
130 ping = 'HeartMonitor.period',
128
131
129 scheme = 'TaskScheduler.scheme_name',
132 scheme = 'TaskScheduler.scheme_name',
130 hwm = 'TaskScheduler.hwm',
133 hwm = 'TaskScheduler.hwm',
131 )
134 )
132 aliases.update(base_aliases)
135 aliases.update(base_aliases)
133
136
137
134 class IPControllerApp(BaseParallelApplication):
138 class IPControllerApp(BaseParallelApplication):
135
139
136 name = u'ipcontroller'
140 name = u'ipcontroller'
137 description = _description
141 description = _description
142 examples = _examples
138 config_file_name = Unicode(default_config_file_name)
143 config_file_name = Unicode(default_config_file_name)
139 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
144 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
140
145
141 # change default to True
146 # change default to True
142 auto_create = Bool(True, config=True,
147 auto_create = Bool(True, config=True,
143 help="""Whether to create profile dir if it doesn't exist.""")
148 help="""Whether to create profile dir if it doesn't exist.""")
144
149
145 reuse_files = Bool(False, config=True,
150 reuse_files = Bool(False, config=True,
146 help='Whether to reuse existing json connection files.'
151 help='Whether to reuse existing json connection files.'
147 )
152 )
148 secure = Bool(True, config=True,
153 secure = Bool(True, config=True,
149 help='Whether to use HMAC digests for extra message authentication.'
154 help='Whether to use HMAC digests for extra message authentication.'
150 )
155 )
151 ssh_server = Unicode(u'', config=True,
156 ssh_server = Unicode(u'', config=True,
152 help="""ssh url for clients to use when connecting to the Controller
157 help="""ssh url for clients to use when connecting to the Controller
153 processes. It should be of the form: [user@]server[:port]. The
158 processes. It should be of the form: [user@]server[:port]. The
154 Controller's listening addresses must be accessible from the ssh server""",
159 Controller's listening addresses must be accessible from the ssh server""",
155 )
160 )
156 location = Unicode(u'', config=True,
161 location = Unicode(u'', config=True,
157 help="""The external IP or domain name of the Controller, used for disambiguating
162 help="""The external IP or domain name of the Controller, used for disambiguating
158 engine and client connections.""",
163 engine and client connections.""",
159 )
164 )
160 import_statements = List([], config=True,
165 import_statements = List([], config=True,
161 help="import statements to be run at startup. Necessary in some environments"
166 help="import statements to be run at startup. Necessary in some environments"
162 )
167 )
163
168
164 use_threads = Bool(False, config=True,
169 use_threads = Bool(False, config=True,
165 help='Use threads instead of processes for the schedulers',
170 help='Use threads instead of processes for the schedulers',
166 )
171 )
167
172
168 # internal
173 # internal
169 children = List()
174 children = List()
170 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
175 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
171
176
172 def _use_threads_changed(self, name, old, new):
177 def _use_threads_changed(self, name, old, new):
173 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
178 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
174
179
175 aliases = Dict(aliases)
180 aliases = Dict(aliases)
176 flags = Dict(flags)
181 flags = Dict(flags)
177
182
178
183
179 def save_connection_dict(self, fname, cdict):
184 def save_connection_dict(self, fname, cdict):
180 """save a connection dict to json file."""
185 """save a connection dict to json file."""
181 c = self.config
186 c = self.config
182 url = cdict['url']
187 url = cdict['url']
183 location = cdict['location']
188 location = cdict['location']
184 if not location:
189 if not location:
185 try:
190 try:
186 proto,ip,port = split_url(url)
191 proto,ip,port = split_url(url)
187 except AssertionError:
192 except AssertionError:
188 pass
193 pass
189 else:
194 else:
190 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
195 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
191 cdict['location'] = location
196 cdict['location'] = location
192 fname = os.path.join(self.profile_dir.security_dir, fname)
197 fname = os.path.join(self.profile_dir.security_dir, fname)
193 with open(fname, 'wb') as f:
198 with open(fname, 'wb') as f:
194 f.write(json.dumps(cdict, indent=2))
199 f.write(json.dumps(cdict, indent=2))
195 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
200 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
196
201
197 def load_config_from_json(self):
202 def load_config_from_json(self):
198 """load config from existing json connector files."""
203 """load config from existing json connector files."""
199 c = self.config
204 c = self.config
200 # load from engine config
205 # load from engine config
201 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-engine.json')) as f:
206 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-engine.json')) as f:
202 cfg = json.loads(f.read())
207 cfg = json.loads(f.read())
203 key = c.Session.key = asbytes(cfg['exec_key'])
208 key = c.Session.key = asbytes(cfg['exec_key'])
204 xport,addr = cfg['url'].split('://')
209 xport,addr = cfg['url'].split('://')
205 c.HubFactory.engine_transport = xport
210 c.HubFactory.engine_transport = xport
206 ip,ports = addr.split(':')
211 ip,ports = addr.split(':')
207 c.HubFactory.engine_ip = ip
212 c.HubFactory.engine_ip = ip
208 c.HubFactory.regport = int(ports)
213 c.HubFactory.regport = int(ports)
209 self.location = cfg['location']
214 self.location = cfg['location']
210 # load client config
215 # load client config
211 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-client.json')) as f:
216 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-client.json')) as f:
212 cfg = json.loads(f.read())
217 cfg = json.loads(f.read())
213 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
218 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
214 xport,addr = cfg['url'].split('://')
219 xport,addr = cfg['url'].split('://')
215 c.HubFactory.client_transport = xport
220 c.HubFactory.client_transport = xport
216 ip,ports = addr.split(':')
221 ip,ports = addr.split(':')
217 c.HubFactory.client_ip = ip
222 c.HubFactory.client_ip = ip
218 self.ssh_server = cfg['ssh']
223 self.ssh_server = cfg['ssh']
219 assert int(ports) == c.HubFactory.regport, "regport mismatch"
224 assert int(ports) == c.HubFactory.regport, "regport mismatch"
220
225
221 def init_hub(self):
226 def init_hub(self):
222 c = self.config
227 c = self.config
223
228
224 self.do_import_statements()
229 self.do_import_statements()
225 reusing = self.reuse_files
230 reusing = self.reuse_files
226 if reusing:
231 if reusing:
227 try:
232 try:
228 self.load_config_from_json()
233 self.load_config_from_json()
229 except (AssertionError,IOError):
234 except (AssertionError,IOError):
230 reusing=False
235 reusing=False
231 # check again, because reusing may have failed:
236 # check again, because reusing may have failed:
232 if reusing:
237 if reusing:
233 pass
238 pass
234 elif self.secure:
239 elif self.secure:
235 key = str(uuid.uuid4())
240 key = str(uuid.uuid4())
236 # keyfile = os.path.join(self.profile_dir.security_dir, self.exec_key)
241 # keyfile = os.path.join(self.profile_dir.security_dir, self.exec_key)
237 # with open(keyfile, 'w') as f:
242 # with open(keyfile, 'w') as f:
238 # f.write(key)
243 # f.write(key)
239 # os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
244 # os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
240 c.Session.key = asbytes(key)
245 c.Session.key = asbytes(key)
241 else:
246 else:
242 key = c.Session.key = b''
247 key = c.Session.key = b''
243
248
244 try:
249 try:
245 self.factory = HubFactory(config=c, log=self.log)
250 self.factory = HubFactory(config=c, log=self.log)
246 # self.start_logging()
251 # self.start_logging()
247 self.factory.init_hub()
252 self.factory.init_hub()
248 except:
253 except:
249 self.log.error("Couldn't construct the Controller", exc_info=True)
254 self.log.error("Couldn't construct the Controller", exc_info=True)
250 self.exit(1)
255 self.exit(1)
251
256
252 if not reusing:
257 if not reusing:
253 # save to new json config files
258 # save to new json config files
254 f = self.factory
259 f = self.factory
255 cdict = {'exec_key' : key,
260 cdict = {'exec_key' : key,
256 'ssh' : self.ssh_server,
261 'ssh' : self.ssh_server,
257 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
262 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
258 'location' : self.location
263 'location' : self.location
259 }
264 }
260 self.save_connection_dict('ipcontroller-client.json', cdict)
265 self.save_connection_dict('ipcontroller-client.json', cdict)
261 edict = cdict
266 edict = cdict
262 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
267 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
263 self.save_connection_dict('ipcontroller-engine.json', edict)
268 self.save_connection_dict('ipcontroller-engine.json', edict)
264
269
265 #
270 #
266 def init_schedulers(self):
271 def init_schedulers(self):
267 children = self.children
272 children = self.children
268 mq = import_item(str(self.mq_class))
273 mq = import_item(str(self.mq_class))
269
274
270 hub = self.factory
275 hub = self.factory
271 # maybe_inproc = 'inproc://monitor' if self.use_threads else self.monitor_url
276 # maybe_inproc = 'inproc://monitor' if self.use_threads else self.monitor_url
272 # IOPub relay (in a Process)
277 # IOPub relay (in a Process)
273 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
278 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
274 q.bind_in(hub.client_info['iopub'])
279 q.bind_in(hub.client_info['iopub'])
275 q.bind_out(hub.engine_info['iopub'])
280 q.bind_out(hub.engine_info['iopub'])
276 q.setsockopt_out(zmq.SUBSCRIBE, b'')
281 q.setsockopt_out(zmq.SUBSCRIBE, b'')
277 q.connect_mon(hub.monitor_url)
282 q.connect_mon(hub.monitor_url)
278 q.daemon=True
283 q.daemon=True
279 children.append(q)
284 children.append(q)
280
285
281 # Multiplexer Queue (in a Process)
286 # Multiplexer Queue (in a Process)
282 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'in', b'out')
287 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'in', b'out')
283 q.bind_in(hub.client_info['mux'])
288 q.bind_in(hub.client_info['mux'])
284 q.setsockopt_in(zmq.IDENTITY, b'mux')
289 q.setsockopt_in(zmq.IDENTITY, b'mux')
285 q.bind_out(hub.engine_info['mux'])
290 q.bind_out(hub.engine_info['mux'])
286 q.connect_mon(hub.monitor_url)
291 q.connect_mon(hub.monitor_url)
287 q.daemon=True
292 q.daemon=True
288 children.append(q)
293 children.append(q)
289
294
290 # Control Queue (in a Process)
295 # Control Queue (in a Process)
291 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'incontrol', b'outcontrol')
296 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, b'incontrol', b'outcontrol')
292 q.bind_in(hub.client_info['control'])
297 q.bind_in(hub.client_info['control'])
293 q.setsockopt_in(zmq.IDENTITY, b'control')
298 q.setsockopt_in(zmq.IDENTITY, b'control')
294 q.bind_out(hub.engine_info['control'])
299 q.bind_out(hub.engine_info['control'])
295 q.connect_mon(hub.monitor_url)
300 q.connect_mon(hub.monitor_url)
296 q.daemon=True
301 q.daemon=True
297 children.append(q)
302 children.append(q)
298 try:
303 try:
299 scheme = self.config.TaskScheduler.scheme_name
304 scheme = self.config.TaskScheduler.scheme_name
300 except AttributeError:
305 except AttributeError:
301 scheme = TaskScheduler.scheme_name.get_default_value()
306 scheme = TaskScheduler.scheme_name.get_default_value()
302 # Task Queue (in a Process)
307 # Task Queue (in a Process)
303 if scheme == 'pure':
308 if scheme == 'pure':
304 self.log.warn("task::using pure XREQ Task scheduler")
309 self.log.warn("task::using pure XREQ Task scheduler")
305 q = mq(zmq.XREP, zmq.XREQ, zmq.PUB, b'intask', b'outtask')
310 q = mq(zmq.XREP, zmq.XREQ, zmq.PUB, b'intask', b'outtask')
306 # q.setsockopt_out(zmq.HWM, hub.hwm)
311 # q.setsockopt_out(zmq.HWM, hub.hwm)
307 q.bind_in(hub.client_info['task'][1])
312 q.bind_in(hub.client_info['task'][1])
308 q.setsockopt_in(zmq.IDENTITY, b'task')
313 q.setsockopt_in(zmq.IDENTITY, b'task')
309 q.bind_out(hub.engine_info['task'])
314 q.bind_out(hub.engine_info['task'])
310 q.connect_mon(hub.monitor_url)
315 q.connect_mon(hub.monitor_url)
311 q.daemon=True
316 q.daemon=True
312 children.append(q)
317 children.append(q)
313 elif scheme == 'none':
318 elif scheme == 'none':
314 self.log.warn("task::using no Task scheduler")
319 self.log.warn("task::using no Task scheduler")
315
320
316 else:
321 else:
317 self.log.info("task::using Python %s Task scheduler"%scheme)
322 self.log.info("task::using Python %s Task scheduler"%scheme)
318 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
323 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
319 hub.monitor_url, hub.client_info['notification'])
324 hub.monitor_url, hub.client_info['notification'])
320 kwargs = dict(logname='scheduler', loglevel=self.log_level,
325 kwargs = dict(logname='scheduler', loglevel=self.log_level,
321 log_url = self.log_url, config=dict(self.config))
326 log_url = self.log_url, config=dict(self.config))
322 if 'Process' in self.mq_class:
327 if 'Process' in self.mq_class:
323 # run the Python scheduler in a Process
328 # run the Python scheduler in a Process
324 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
329 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
325 q.daemon=True
330 q.daemon=True
326 children.append(q)
331 children.append(q)
327 else:
332 else:
328 # single-threaded Controller
333 # single-threaded Controller
329 kwargs['in_thread'] = True
334 kwargs['in_thread'] = True
330 launch_scheduler(*sargs, **kwargs)
335 launch_scheduler(*sargs, **kwargs)
331
336
332
337
333 def save_urls(self):
338 def save_urls(self):
334 """save the registration urls to files."""
339 """save the registration urls to files."""
335 c = self.config
340 c = self.config
336
341
337 sec_dir = self.profile_dir.security_dir
342 sec_dir = self.profile_dir.security_dir
338 cf = self.factory
343 cf = self.factory
339
344
340 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
345 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
341 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
346 f.write("%s://%s:%s"%(cf.engine_transport, cf.engine_ip, cf.regport))
342
347
343 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
348 with open(os.path.join(sec_dir, 'ipcontroller-client.url'), 'w') as f:
344 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
349 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
345
350
346
351
347 def do_import_statements(self):
352 def do_import_statements(self):
348 statements = self.import_statements
353 statements = self.import_statements
349 for s in statements:
354 for s in statements:
350 try:
355 try:
351 self.log.msg("Executing statement: '%s'" % s)
356 self.log.msg("Executing statement: '%s'" % s)
352 exec s in globals(), locals()
357 exec s in globals(), locals()
353 except:
358 except:
354 self.log.msg("Error running statement: %s" % s)
359 self.log.msg("Error running statement: %s" % s)
355
360
356 def forward_logging(self):
361 def forward_logging(self):
357 if self.log_url:
362 if self.log_url:
358 self.log.info("Forwarding logging to %s"%self.log_url)
363 self.log.info("Forwarding logging to %s"%self.log_url)
359 context = zmq.Context.instance()
364 context = zmq.Context.instance()
360 lsock = context.socket(zmq.PUB)
365 lsock = context.socket(zmq.PUB)
361 lsock.connect(self.log_url)
366 lsock.connect(self.log_url)
362 handler = PUBHandler(lsock)
367 handler = PUBHandler(lsock)
363 self.log.removeHandler(self._log_handler)
368 self.log.removeHandler(self._log_handler)
364 handler.root_topic = 'controller'
369 handler.root_topic = 'controller'
365 handler.setLevel(self.log_level)
370 handler.setLevel(self.log_level)
366 self.log.addHandler(handler)
371 self.log.addHandler(handler)
367 self._log_handler = handler
372 self._log_handler = handler
368 # #
373 # #
369
374
370 def initialize(self, argv=None):
375 def initialize(self, argv=None):
371 super(IPControllerApp, self).initialize(argv)
376 super(IPControllerApp, self).initialize(argv)
372 self.forward_logging()
377 self.forward_logging()
373 self.init_hub()
378 self.init_hub()
374 self.init_schedulers()
379 self.init_schedulers()
375
380
376 def start(self):
381 def start(self):
377 # Start the subprocesses:
382 # Start the subprocesses:
378 self.factory.start()
383 self.factory.start()
379 child_procs = []
384 child_procs = []
380 for child in self.children:
385 for child in self.children:
381 child.start()
386 child.start()
382 if isinstance(child, ProcessMonitoredQueue):
387 if isinstance(child, ProcessMonitoredQueue):
383 child_procs.append(child.launcher)
388 child_procs.append(child.launcher)
384 elif isinstance(child, Process):
389 elif isinstance(child, Process):
385 child_procs.append(child)
390 child_procs.append(child)
386 if child_procs:
391 if child_procs:
387 signal_children(child_procs)
392 signal_children(child_procs)
388
393
389 self.write_pid_file(overwrite=True)
394 self.write_pid_file(overwrite=True)
390
395
391 try:
396 try:
392 self.factory.loop.start()
397 self.factory.loop.start()
393 except KeyboardInterrupt:
398 except KeyboardInterrupt:
394 self.log.critical("Interrupted, Exiting...\n")
399 self.log.critical("Interrupted, Exiting...\n")
395
400
396
401
397
402
398 def launch_new_instance():
403 def launch_new_instance():
399 """Create and run the IPython controller"""
404 """Create and run the IPython controller"""
400 if sys.platform == 'win32':
405 if sys.platform == 'win32':
401 # make sure we don't get called from a multiprocessing subprocess
406 # make sure we don't get called from a multiprocessing subprocess
402 # this can result in infinite Controllers being started on Windows
407 # this can result in infinite Controllers being started on Windows
403 # which doesn't have a proper fork, so multiprocessing is wonky
408 # which doesn't have a proper fork, so multiprocessing is wonky
404
409
405 # this only comes up when IPython has been installed using vanilla
410 # this only comes up when IPython has been installed using vanilla
406 # setuptools, and *not* distribute.
411 # setuptools, and *not* distribute.
407 import multiprocessing
412 import multiprocessing
408 p = multiprocessing.current_process()
413 p = multiprocessing.current_process()
409 # the main process has name 'MainProcess'
414 # the main process has name 'MainProcess'
410 # subprocesses will have names like 'Process-1'
415 # subprocesses will have names like 'Process-1'
411 if p.name != 'MainProcess':
416 if p.name != 'MainProcess':
412 # we are a subprocess, don't start another Controller!
417 # we are a subprocess, don't start another Controller!
413 return
418 return
414 app = IPControllerApp.instance()
419 app = IPControllerApp.instance()
415 app.initialize()
420 app.initialize()
416 app.start()
421 app.start()
417
422
418
423
419 if __name__ == '__main__':
424 if __name__ == '__main__':
420 launch_new_instance()
425 launch_new_instance()
@@ -1,301 +1,307 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 import time
27 import time
28
28
29 import zmq
29 import zmq
30 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
31
31
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.parallel.apps.baseapp import (
33 from IPython.parallel.apps.baseapp import (
34 BaseParallelApplication,
34 BaseParallelApplication,
35 base_aliases,
35 base_aliases,
36 base_flags,
36 base_flags,
37 )
37 )
38 from IPython.zmq.log import EnginePUBHandler
38 from IPython.zmq.log import EnginePUBHandler
39
39
40 from IPython.config.configurable import Configurable
40 from IPython.config.configurable import Configurable
41 from IPython.zmq.session import Session
41 from IPython.zmq.session import Session
42 from IPython.parallel.engine.engine import EngineFactory
42 from IPython.parallel.engine.engine import EngineFactory
43 from IPython.parallel.engine.streamkernel import Kernel
43 from IPython.parallel.engine.streamkernel import Kernel
44 from IPython.parallel.util import disambiguate_url, asbytes
44 from IPython.parallel.util import disambiguate_url, asbytes
45
45
46 from IPython.utils.importstring import import_item
46 from IPython.utils.importstring import import_item
47 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float
47 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float
48
48
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Module level variables
51 # Module level variables
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54 #: The default config file name for this application
54 #: The default config file name for this application
55 default_config_file_name = u'ipengine_config.py'
55 default_config_file_name = u'ipengine_config.py'
56
56
57 _description = """Start an IPython engine for parallel computing.
57 _description = """Start an IPython engine for parallel computing.
58
58
59 IPython engines run in parallel and perform computations on behalf of a client
59 IPython engines run in parallel and perform computations on behalf of a client
60 and controller. A controller needs to be started before the engines. The
60 and controller. A controller needs to be started before the engines. The
61 engine can be configured using command line options or using a cluster
61 engine can be configured using command line options or using a cluster
62 directory. Cluster directories contain config, log and security files and are
62 directory. Cluster directories contain config, log and security files and are
63 usually located in your ipython directory and named as "profile_name".
63 usually located in your ipython directory and named as "profile_name".
64 See the `profile` and `profile_dir` options for details.
64 See the `profile` and `profile_dir` options for details.
65 """
65 """
66
66
67 _examples = """
68 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
69 ipengine --log_to_file --log_level=DEBUG # log to a file with DEBUG verbosity
70 """
67
71
68 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
69 # MPI configuration
73 # MPI configuration
70 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
71
75
72 mpi4py_init = """from mpi4py import MPI as mpi
76 mpi4py_init = """from mpi4py import MPI as mpi
73 mpi.size = mpi.COMM_WORLD.Get_size()
77 mpi.size = mpi.COMM_WORLD.Get_size()
74 mpi.rank = mpi.COMM_WORLD.Get_rank()
78 mpi.rank = mpi.COMM_WORLD.Get_rank()
75 """
79 """
76
80
77
81
78 pytrilinos_init = """from PyTrilinos import Epetra
82 pytrilinos_init = """from PyTrilinos import Epetra
79 class SimpleStruct:
83 class SimpleStruct:
80 pass
84 pass
81 mpi = SimpleStruct()
85 mpi = SimpleStruct()
82 mpi.rank = 0
86 mpi.rank = 0
83 mpi.size = 0
87 mpi.size = 0
84 """
88 """
85
89
86 class MPI(Configurable):
90 class MPI(Configurable):
87 """Configurable for MPI initialization"""
91 """Configurable for MPI initialization"""
88 use = Unicode('', config=True,
92 use = Unicode('', config=True,
89 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
93 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
90 )
94 )
91
95
92 def _on_use_changed(self, old, new):
96 def _on_use_changed(self, old, new):
93 # load default init script if it's not set
97 # load default init script if it's not set
94 if not self.init_script:
98 if not self.init_script:
95 self.init_script = self.default_inits.get(new, '')
99 self.init_script = self.default_inits.get(new, '')
96
100
97 init_script = Unicode('', config=True,
101 init_script = Unicode('', config=True,
98 help="Initialization code for MPI")
102 help="Initialization code for MPI")
99
103
100 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
104 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
101 config=True)
105 config=True)
102
106
103
107
104 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
105 # Main application
109 # Main application
106 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
107 aliases = dict(
111 aliases = dict(
108 file = 'IPEngineApp.url_file',
112 file = 'IPEngineApp.url_file',
109 c = 'IPEngineApp.startup_command',
113 c = 'IPEngineApp.startup_command',
110 s = 'IPEngineApp.startup_script',
114 s = 'IPEngineApp.startup_script',
111
115
112 ident = 'Session.session',
116 ident = 'Session.session',
113 user = 'Session.username',
117 user = 'Session.username',
114 keyfile = 'Session.keyfile',
118 keyfile = 'Session.keyfile',
115
119
116 url = 'EngineFactory.url',
120 url = 'EngineFactory.url',
117 ip = 'EngineFactory.ip',
121 ip = 'EngineFactory.ip',
118 transport = 'EngineFactory.transport',
122 transport = 'EngineFactory.transport',
119 port = 'EngineFactory.regport',
123 port = 'EngineFactory.regport',
120 location = 'EngineFactory.location',
124 location = 'EngineFactory.location',
121
125
122 timeout = 'EngineFactory.timeout',
126 timeout = 'EngineFactory.timeout',
123
127
124 mpi = 'MPI.use',
128 mpi = 'MPI.use',
125
129
126 )
130 )
127 aliases.update(base_aliases)
131 aliases.update(base_aliases)
128
132
133
129 class IPEngineApp(BaseParallelApplication):
134 class IPEngineApp(BaseParallelApplication):
130
135
131 name = Unicode(u'ipengine')
136 name = Unicode(u'ipengine')
132 description = Unicode(_description)
137 description = Unicode(_description)
138 examples = _examples
133 config_file_name = Unicode(default_config_file_name)
139 config_file_name = Unicode(default_config_file_name)
134 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
140 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
135
141
136 startup_script = Unicode(u'', config=True,
142 startup_script = Unicode(u'', config=True,
137 help='specify a script to be run at startup')
143 help='specify a script to be run at startup')
138 startup_command = Unicode('', config=True,
144 startup_command = Unicode('', config=True,
139 help='specify a command to be run at startup')
145 help='specify a command to be run at startup')
140
146
141 url_file = Unicode(u'', config=True,
147 url_file = Unicode(u'', config=True,
142 help="""The full location of the file containing the connection information for
148 help="""The full location of the file containing the connection information for
143 the controller. If this is not given, the file must be in the
149 the controller. If this is not given, the file must be in the
144 security directory of the cluster directory. This location is
150 security directory of the cluster directory. This location is
145 resolved using the `profile` or `profile_dir` options.""",
151 resolved using the `profile` or `profile_dir` options.""",
146 )
152 )
147 wait_for_url_file = Float(5, config=True,
153 wait_for_url_file = Float(5, config=True,
148 help="""The maximum number of seconds to wait for url_file to exist.
154 help="""The maximum number of seconds to wait for url_file to exist.
149 This is useful for batch-systems and shared-filesystems where the
155 This is useful for batch-systems and shared-filesystems where the
150 controller and engine are started at the same time and it
156 controller and engine are started at the same time and it
151 may take a moment for the controller to write the connector files.""")
157 may take a moment for the controller to write the connector files.""")
152
158
153 url_file_name = Unicode(u'ipcontroller-engine.json')
159 url_file_name = Unicode(u'ipcontroller-engine.json')
154 log_url = Unicode('', config=True,
160 log_url = Unicode('', config=True,
155 help="""The URL for the iploggerapp instance, for forwarding
161 help="""The URL for the iploggerapp instance, for forwarding
156 logging to a central location.""")
162 logging to a central location.""")
157
163
158 aliases = Dict(aliases)
164 aliases = Dict(aliases)
159
165
160 # def find_key_file(self):
166 # def find_key_file(self):
161 # """Set the key file.
167 # """Set the key file.
162 #
168 #
163 # Here we don't try to actually see if it exists for is valid as that
169 # Here we don't try to actually see if it exists for is valid as that
164 # is hadled by the connection logic.
170 # is hadled by the connection logic.
165 # """
171 # """
166 # config = self.master_config
172 # config = self.master_config
167 # # Find the actual controller key file
173 # # Find the actual controller key file
168 # if not config.Global.key_file:
174 # if not config.Global.key_file:
169 # try_this = os.path.join(
175 # try_this = os.path.join(
170 # config.Global.profile_dir,
176 # config.Global.profile_dir,
171 # config.Global.security_dir,
177 # config.Global.security_dir,
172 # config.Global.key_file_name
178 # config.Global.key_file_name
173 # )
179 # )
174 # config.Global.key_file = try_this
180 # config.Global.key_file = try_this
175
181
176 def find_url_file(self):
182 def find_url_file(self):
177 """Set the url file.
183 """Set the url file.
178
184
179 Here we don't try to actually see if it exists for is valid as that
185 Here we don't try to actually see if it exists for is valid as that
180 is hadled by the connection logic.
186 is hadled by the connection logic.
181 """
187 """
182 config = self.config
188 config = self.config
183 # Find the actual controller key file
189 # Find the actual controller key file
184 if not self.url_file:
190 if not self.url_file:
185 self.url_file = os.path.join(
191 self.url_file = os.path.join(
186 self.profile_dir.security_dir,
192 self.profile_dir.security_dir,
187 self.url_file_name
193 self.url_file_name
188 )
194 )
189 def init_engine(self):
195 def init_engine(self):
190 # This is the working dir by now.
196 # This is the working dir by now.
191 sys.path.insert(0, '')
197 sys.path.insert(0, '')
192 config = self.config
198 config = self.config
193 # print config
199 # print config
194 self.find_url_file()
200 self.find_url_file()
195
201
196 # was the url manually specified?
202 # was the url manually specified?
197 keys = set(self.config.EngineFactory.keys())
203 keys = set(self.config.EngineFactory.keys())
198 keys = keys.union(set(self.config.RegistrationFactory.keys()))
204 keys = keys.union(set(self.config.RegistrationFactory.keys()))
199
205
200 if keys.intersection(set(['ip', 'url', 'port'])):
206 if keys.intersection(set(['ip', 'url', 'port'])):
201 # Connection info was specified, don't wait for the file
207 # Connection info was specified, don't wait for the file
202 url_specified = True
208 url_specified = True
203 self.wait_for_url_file = 0
209 self.wait_for_url_file = 0
204 else:
210 else:
205 url_specified = False
211 url_specified = False
206
212
207 if self.wait_for_url_file and not os.path.exists(self.url_file):
213 if self.wait_for_url_file and not os.path.exists(self.url_file):
208 self.log.warn("url_file %r not found"%self.url_file)
214 self.log.warn("url_file %r not found"%self.url_file)
209 self.log.warn("Waiting up to %.1f seconds for it to arrive."%self.wait_for_url_file)
215 self.log.warn("Waiting up to %.1f seconds for it to arrive."%self.wait_for_url_file)
210 tic = time.time()
216 tic = time.time()
211 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
217 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
212 # wait for url_file to exist, for up to 10 seconds
218 # wait for url_file to exist, for up to 10 seconds
213 time.sleep(0.1)
219 time.sleep(0.1)
214
220
215 if os.path.exists(self.url_file):
221 if os.path.exists(self.url_file):
216 self.log.info("Loading url_file %r"%self.url_file)
222 self.log.info("Loading url_file %r"%self.url_file)
217 with open(self.url_file) as f:
223 with open(self.url_file) as f:
218 d = json.loads(f.read())
224 d = json.loads(f.read())
219 if d['exec_key']:
225 if d['exec_key']:
220 config.Session.key = asbytes(d['exec_key'])
226 config.Session.key = asbytes(d['exec_key'])
221 d['url'] = disambiguate_url(d['url'], d['location'])
227 d['url'] = disambiguate_url(d['url'], d['location'])
222 config.EngineFactory.url = d['url']
228 config.EngineFactory.url = d['url']
223 config.EngineFactory.location = d['location']
229 config.EngineFactory.location = d['location']
224 elif not url_specified:
230 elif not url_specified:
225 self.log.critical("Fatal: url file never arrived: %s"%self.url_file)
231 self.log.critical("Fatal: url file never arrived: %s"%self.url_file)
226 self.exit(1)
232 self.exit(1)
227
233
228
234
229 try:
235 try:
230 exec_lines = config.Kernel.exec_lines
236 exec_lines = config.Kernel.exec_lines
231 except AttributeError:
237 except AttributeError:
232 config.Kernel.exec_lines = []
238 config.Kernel.exec_lines = []
233 exec_lines = config.Kernel.exec_lines
239 exec_lines = config.Kernel.exec_lines
234
240
235 if self.startup_script:
241 if self.startup_script:
236 enc = sys.getfilesystemencoding() or 'utf8'
242 enc = sys.getfilesystemencoding() or 'utf8'
237 cmd="execfile(%r)"%self.startup_script.encode(enc)
243 cmd="execfile(%r)"%self.startup_script.encode(enc)
238 exec_lines.append(cmd)
244 exec_lines.append(cmd)
239 if self.startup_command:
245 if self.startup_command:
240 exec_lines.append(self.startup_command)
246 exec_lines.append(self.startup_command)
241
247
242 # Create the underlying shell class and Engine
248 # Create the underlying shell class and Engine
243 # shell_class = import_item(self.master_config.Global.shell_class)
249 # shell_class = import_item(self.master_config.Global.shell_class)
244 # print self.config
250 # print self.config
245 try:
251 try:
246 self.engine = EngineFactory(config=config, log=self.log)
252 self.engine = EngineFactory(config=config, log=self.log)
247 except:
253 except:
248 self.log.error("Couldn't start the Engine", exc_info=True)
254 self.log.error("Couldn't start the Engine", exc_info=True)
249 self.exit(1)
255 self.exit(1)
250
256
251 def forward_logging(self):
257 def forward_logging(self):
252 if self.log_url:
258 if self.log_url:
253 self.log.info("Forwarding logging to %s"%self.log_url)
259 self.log.info("Forwarding logging to %s"%self.log_url)
254 context = self.engine.context
260 context = self.engine.context
255 lsock = context.socket(zmq.PUB)
261 lsock = context.socket(zmq.PUB)
256 lsock.connect(self.log_url)
262 lsock.connect(self.log_url)
257 self.log.removeHandler(self._log_handler)
263 self.log.removeHandler(self._log_handler)
258 handler = EnginePUBHandler(self.engine, lsock)
264 handler = EnginePUBHandler(self.engine, lsock)
259 handler.setLevel(self.log_level)
265 handler.setLevel(self.log_level)
260 self.log.addHandler(handler)
266 self.log.addHandler(handler)
261 self._log_handler = handler
267 self._log_handler = handler
262 #
268 #
263 def init_mpi(self):
269 def init_mpi(self):
264 global mpi
270 global mpi
265 self.mpi = MPI(config=self.config)
271 self.mpi = MPI(config=self.config)
266
272
267 mpi_import_statement = self.mpi.init_script
273 mpi_import_statement = self.mpi.init_script
268 if mpi_import_statement:
274 if mpi_import_statement:
269 try:
275 try:
270 self.log.info("Initializing MPI:")
276 self.log.info("Initializing MPI:")
271 self.log.info(mpi_import_statement)
277 self.log.info(mpi_import_statement)
272 exec mpi_import_statement in globals()
278 exec mpi_import_statement in globals()
273 except:
279 except:
274 mpi = None
280 mpi = None
275 else:
281 else:
276 mpi = None
282 mpi = None
277
283
278 def initialize(self, argv=None):
284 def initialize(self, argv=None):
279 super(IPEngineApp, self).initialize(argv)
285 super(IPEngineApp, self).initialize(argv)
280 self.init_mpi()
286 self.init_mpi()
281 self.init_engine()
287 self.init_engine()
282 self.forward_logging()
288 self.forward_logging()
283
289
284 def start(self):
290 def start(self):
285 self.engine.start()
291 self.engine.start()
286 try:
292 try:
287 self.engine.loop.start()
293 self.engine.loop.start()
288 except KeyboardInterrupt:
294 except KeyboardInterrupt:
289 self.log.critical("Engine Interrupted, shutting down...\n")
295 self.log.critical("Engine Interrupted, shutting down...\n")
290
296
291
297
292 def launch_new_instance():
298 def launch_new_instance():
293 """Create and run the IPython engine"""
299 """Create and run the IPython engine"""
294 app = IPEngineApp.instance()
300 app = IPEngineApp.instance()
295 app.initialize()
301 app.initialize()
296 app.start()
302 app.start()
297
303
298
304
299 if __name__ == '__main__':
305 if __name__ == '__main__':
300 launch_new_instance()
306 launch_new_instance()
301
307
General Comments 0
You need to be logged in to leave comments. Login now