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