##// END OF EJS Templates
command-line pass...
MinRK -
Show More
@@ -1,307 +1,309 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008-2011 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import glob
31 31 import logging
32 32 import os
33 33 import shutil
34 34 import sys
35 35
36 36 from IPython.config.application import Application
37 37 from IPython.config.configurable import Configurable
38 38 from IPython.config.loader import Config
39 39 from IPython.core import release, crashhandler
40 40 from IPython.core.profiledir import ProfileDir, ProfileDirError
41 41 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
42 42 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Classes and functions
46 46 #-----------------------------------------------------------------------------
47 47
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Base Application Class
51 51 #-----------------------------------------------------------------------------
52 52
53 53 # aliases and flags
54 54
55 55 base_aliases = {
56 56 'profile' : 'BaseIPythonApplication.profile',
57 57 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
58 58 'log-level' : 'Application.log_level',
59 59 }
60 60
61 61 base_flags = dict(
62 62 debug = ({'Application' : {'log_level' : logging.DEBUG}},
63 63 "set log level to logging.DEBUG (maximize logging output)"),
64 64 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
65 65 "set log level to logging.CRITICAL (minimize logging output)"),
66 66 init = ({'BaseIPythonApplication' : {
67 67 'copy_config_files' : True,
68 68 'auto_create' : True}
69 }, "Initialize profile with default config files")
69 }, """Initialize profile with default config files. This is equivalent
70 to running `ipython profile create <profile>` prior to startup.
71 """)
70 72 )
71 73
72 74
73 75 class BaseIPythonApplication(Application):
74 76
75 77 name = Unicode(u'ipython')
76 78 description = Unicode(u'IPython: an enhanced interactive Python shell.')
77 79 version = Unicode(release.version)
78 80
79 81 aliases = Dict(base_aliases)
80 82 flags = Dict(base_flags)
81 83 classes = List([ProfileDir])
82 84
83 85 # Track whether the config_file has changed,
84 86 # because some logic happens only if we aren't using the default.
85 87 config_file_specified = Bool(False)
86 88
87 89 config_file_name = Unicode(u'ipython_config.py')
88 90 def _config_file_name_default(self):
89 91 return self.name.replace('-','_') + u'_config.py'
90 92 def _config_file_name_changed(self, name, old, new):
91 93 if new != old:
92 94 self.config_file_specified = True
93 95
94 96 # The directory that contains IPython's builtin profiles.
95 97 builtin_profile_dir = Unicode(
96 98 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
97 99 )
98 100
99 101 config_file_paths = List(Unicode)
100 102 def _config_file_paths_default(self):
101 103 return [os.getcwdu()]
102 104
103 105 profile = Unicode(u'default', config=True,
104 106 help="""The IPython profile to use."""
105 107 )
106 108 def _profile_changed(self, name, old, new):
107 109 self.builtin_profile_dir = os.path.join(
108 110 get_ipython_package_dir(), u'config', u'profile', new
109 111 )
110 112
111 113 ipython_dir = Unicode(get_ipython_dir(), config=True,
112 114 help="""
113 115 The name of the IPython directory. This directory is used for logging
114 116 configuration (through profiles), history storage, etc. The default
115 117 is usually $HOME/.ipython. This options can also be specified through
116 118 the environment variable IPYTHON_DIR.
117 119 """
118 120 )
119 121
120 122 overwrite = Bool(False, config=True,
121 123 help="""Whether to overwrite existing config files when copying""")
122 124 auto_create = Bool(False, config=True,
123 125 help="""Whether to create profile dir if it doesn't exist""")
124 126
125 127 config_files = List(Unicode)
126 128 def _config_files_default(self):
127 129 return [u'ipython_config.py']
128 130
129 131 copy_config_files = Bool(False, config=True,
130 132 help="""Whether to install the default config files into the profile dir.
131 133 If a new profile is being created, and IPython contains config files for that
132 134 profile, then they will be staged into the new directory. Otherwise,
133 135 default config files will be automatically generated.
134 136 """)
135 137
136 138 # The class to use as the crash handler.
137 139 crash_handler_class = Type(crashhandler.CrashHandler)
138 140
139 141 def __init__(self, **kwargs):
140 142 super(BaseIPythonApplication, self).__init__(**kwargs)
141 143 # ensure even default IPYTHON_DIR exists
142 144 if not os.path.exists(self.ipython_dir):
143 145 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
144 146
145 147 #-------------------------------------------------------------------------
146 148 # Various stages of Application creation
147 149 #-------------------------------------------------------------------------
148 150
149 151 def init_crash_handler(self):
150 152 """Create a crash handler, typically setting sys.excepthook to it."""
151 153 self.crash_handler = self.crash_handler_class(self)
152 154 sys.excepthook = self.crash_handler
153 155
154 156 def _ipython_dir_changed(self, name, old, new):
155 157 if old in sys.path:
156 158 sys.path.remove(old)
157 159 sys.path.append(os.path.abspath(new))
158 160 if not os.path.isdir(new):
159 161 os.makedirs(new, mode=0777)
160 162 readme = os.path.join(new, 'README')
161 163 if not os.path.exists(readme):
162 164 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
163 165 shutil.copy(os.path.join(path, 'README'), readme)
164 166 self.log.debug("IPYTHON_DIR set to: %s" % new)
165 167
166 168 def load_config_file(self, suppress_errors=True):
167 169 """Load the config file.
168 170
169 171 By default, errors in loading config are handled, and a warning
170 172 printed on screen. For testing, the suppress_errors option is set
171 173 to False, so errors will make tests fail.
172 174 """
173 175 base_config = 'ipython_config.py'
174 176 self.log.debug("Attempting to load config file: %s" %
175 177 base_config)
176 178 try:
177 179 Application.load_config_file(
178 180 self,
179 181 base_config,
180 182 path=self.config_file_paths
181 183 )
182 184 except IOError:
183 185 # ignore errors loading parent
184 186 pass
185 187 if self.config_file_name == base_config:
186 188 # don't load secondary config
187 189 return
188 190 self.log.debug("Attempting to load config file: %s" %
189 191 self.config_file_name)
190 192 try:
191 193 Application.load_config_file(
192 194 self,
193 195 self.config_file_name,
194 196 path=self.config_file_paths
195 197 )
196 198 except IOError:
197 199 # Only warn if the default config file was NOT being used.
198 200 if self.config_file_specified:
199 201 self.log.warn("Config file not found, skipping: %s" %
200 202 self.config_file_name)
201 203 except:
202 204 # For testing purposes.
203 205 if not suppress_errors:
204 206 raise
205 207 self.log.warn("Error loading config file: %s" %
206 208 self.config_file_name, exc_info=True)
207 209
208 210 def init_profile_dir(self):
209 211 """initialize the profile dir"""
210 212 try:
211 213 # location explicitly specified:
212 214 location = self.config.ProfileDir.location
213 215 except AttributeError:
214 216 # location not specified, find by profile name
215 217 try:
216 218 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
217 219 except ProfileDirError:
218 220 # not found, maybe create it (always create default profile)
219 221 if self.auto_create or self.profile=='default':
220 222 try:
221 223 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
222 224 except ProfileDirError:
223 225 self.log.fatal("Could not create profile: %r"%self.profile)
224 226 self.exit(1)
225 227 else:
226 228 self.log.info("Created profile dir: %r"%p.location)
227 229 else:
228 230 self.log.fatal("Profile %r not found."%self.profile)
229 231 self.exit(1)
230 232 else:
231 233 self.log.info("Using existing profile dir: %r"%p.location)
232 234 else:
233 235 # location is fully specified
234 236 try:
235 237 p = ProfileDir.find_profile_dir(location, self.config)
236 238 except ProfileDirError:
237 239 # not found, maybe create it
238 240 if self.auto_create:
239 241 try:
240 242 p = ProfileDir.create_profile_dir(location, self.config)
241 243 except ProfileDirError:
242 244 self.log.fatal("Could not create profile directory: %r"%location)
243 245 self.exit(1)
244 246 else:
245 247 self.log.info("Creating new profile dir: %r"%location)
246 248 else:
247 249 self.log.fatal("Profile directory %r not found."%location)
248 250 self.exit(1)
249 251 else:
250 252 self.log.info("Using existing profile dir: %r"%location)
251 253
252 254 self.profile_dir = p
253 255 self.config_file_paths.append(p.location)
254 256
255 257 def init_config_files(self):
256 258 """[optionally] copy default config files into profile dir."""
257 259 # copy config files
258 260 path = self.builtin_profile_dir
259 261 if self.copy_config_files:
260 262 src = self.profile
261 263
262 264 cfg = self.config_file_name
263 265 if path and os.path.exists(os.path.join(path, cfg)):
264 266 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
265 267 cfg, src, self.profile_dir.location, self.overwrite)
266 268 )
267 269 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
268 270 else:
269 271 self.stage_default_config_file()
270 272 else:
271 273 # Still stage *bundled* config files, but not generated ones
272 274 # This is necessary for `ipython profile=sympy` to load the profile
273 275 # on the first go
274 276 files = glob.glob(os.path.join(path, '*.py'))
275 277 for fullpath in files:
276 278 cfg = os.path.basename(fullpath)
277 279 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
278 280 # file was copied
279 281 self.log.warn("Staging bundled %s from %s into %r"%(
280 282 cfg, self.profile, self.profile_dir.location)
281 283 )
282 284
283 285
284 286 def stage_default_config_file(self):
285 287 """auto generate default config file, and stage it into the profile."""
286 288 s = self.generate_config_file()
287 289 fname = os.path.join(self.profile_dir.location, self.config_file_name)
288 290 if self.overwrite or not os.path.exists(fname):
289 291 self.log.warn("Generating default config file: %r"%(fname))
290 292 with open(fname, 'w') as f:
291 293 f.write(s)
292 294
293 295
294 296 def initialize(self, argv=None):
295 297 # don't hook up crash handler before parsing command-line
296 298 self.parse_command_line(argv)
297 299 self.init_crash_handler()
298 300 if self.subapp is not None:
299 301 # stop here if subapp is taking over
300 302 return
301 303 cl_config = self.config
302 304 self.init_profile_dir()
303 305 self.init_config_files()
304 306 self.load_config_file()
305 307 # enforce cl-opts override configfile opts:
306 308 self.update_config(cl_config)
307 309
@@ -1,238 +1,240 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 77 _list_examples = "ipython profile list # list all profiles"
78 78
79 79 _create_examples = """
80 80 ipython profile create foo # create profile foo w/ default config files
81 81 ipython profile create foo --reset # restage default config files over current
82 82 ipython profile create foo --parallel # also stage parallel config files
83 83 """
84 84
85 85 _main_examples = """
86 86 ipython profile create -h # show the help string for the create subcommand
87 87 ipython profile list -h # show the help string for the list subcommand
88 88 """
89 89
90 90 #-----------------------------------------------------------------------------
91 91 # Profile Application Class (for `ipython profile` subcommand)
92 92 #-----------------------------------------------------------------------------
93 93
94 94
95 95 class ProfileList(Application):
96 96 name = u'ipython-profile'
97 97 description = list_help
98 98 examples = _list_examples
99 99
100 100 aliases = Dict({
101 101 'ipython-dir' : 'ProfileList.ipython_dir',
102 102 'log-level' : 'Application.log_level',
103 103 })
104 104 flags = Dict(dict(
105 105 debug = ({'Application' : {'log_level' : 0}},
106 106 "Set Application.log_level to 0, maximizing log output."
107 107 )
108 108 ))
109 109
110 110 ipython_dir = Unicode(get_ipython_dir(), config=True,
111 111 help="""
112 112 The name of the IPython directory. This directory is used for logging
113 113 configuration (through profiles), history storage, etc. The default
114 114 is usually $HOME/.ipython. This options can also be specified through
115 115 the environment variable IPYTHON_DIR.
116 116 """
117 117 )
118 118
119 119 def list_profile_dirs(self):
120 120 # Find the search paths
121 121 paths = [os.getcwdu(), self.ipython_dir]
122 122
123 123 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
124 124 for path in paths:
125 125 files = os.listdir(path)
126 126 for f in files:
127 127 full_path = os.path.join(path, f)
128 128 if os.path.isdir(full_path) and f.startswith('profile_'):
129 129 profile = f.split('_',1)[-1]
130 130 start_cmd = 'ipython profile=%s' % profile
131 131 print start_cmd + " ==> " + full_path
132 132
133 133 def start(self):
134 134 self.list_profile_dirs()
135 135
136 136
137 137 create_flags = {}
138 138 create_flags.update(base_flags)
139 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
140 "reset config files to defaults", "leave existing config files"))
141 create_flags.update(boolean_flag('parallel', 'ProfileCreate.parallel',
142 "Include parallel computing config files",
143 "Don't include parallel computing config files"))
139 # don't include '--init' flag, which implies running profile create in other apps
140 create_flags.pop('init')
141 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
142 "reset config files in this profile to the defaults.")
143 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
144 "Include the config files for parallel "
145 "computing apps (ipengine, ipcontroller, etc.)")
144 146
145 147
146 148 class ProfileCreate(BaseIPythonApplication):
147 149 name = u'ipython-profile'
148 150 description = create_help
149 151 examples = _create_examples
150 152 auto_create = Bool(True, config=False)
151 153
152 154 def _copy_config_files_default(self):
153 155 return True
154 156
155 157 parallel = Bool(False, config=True,
156 158 help="whether to include parallel computing config files")
157 159 def _parallel_changed(self, name, old, new):
158 160 parallel_files = [ 'ipcontroller_config.py',
159 161 'ipengine_config.py',
160 162 'ipcluster_config.py'
161 163 ]
162 164 if new:
163 165 for cf in parallel_files:
164 166 self.config_files.append(cf)
165 167 else:
166 168 for cf in parallel_files:
167 169 if cf in self.config_files:
168 170 self.config_files.remove(cf)
169 171
170 172 def parse_command_line(self, argv):
171 173 super(ProfileCreate, self).parse_command_line(argv)
172 174 # accept positional arg as profile name
173 175 if self.extra_args:
174 176 self.profile = self.extra_args[0]
175 177
176 178 flags = Dict(create_flags)
177 179
178 180 classes = [ProfileDir]
179 181
180 182 def init_config_files(self):
181 183 super(ProfileCreate, self).init_config_files()
182 184 # use local imports, since these classes may import from here
183 185 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
184 186 apps = [TerminalIPythonApp]
185 187 try:
186 188 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
187 189 except Exception:
188 190 # this should be ImportError, but under weird circumstances
189 191 # this might be an AttributeError, or possibly others
190 192 # in any case, nothing should cause the profile creation to crash.
191 193 pass
192 194 else:
193 195 apps.append(IPythonQtConsoleApp)
194 196 if self.parallel:
195 197 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
196 198 from IPython.parallel.apps.ipengineapp import IPEngineApp
197 199 from IPython.parallel.apps.ipclusterapp import IPClusterStart
198 200 from IPython.parallel.apps.iploggerapp import IPLoggerApp
199 201 apps.extend([
200 202 IPControllerApp,
201 203 IPEngineApp,
202 204 IPClusterStart,
203 205 IPLoggerApp,
204 206 ])
205 207 for App in apps:
206 208 app = App()
207 209 app.config.update(self.config)
208 210 app.log = self.log
209 211 app.overwrite = self.overwrite
210 212 app.copy_config_files=True
211 213 app.profile = self.profile
212 214 app.init_profile_dir()
213 215 app.init_config_files()
214 216
215 217 def stage_default_config_file(self):
216 218 pass
217 219
218 220
219 221 class ProfileApp(Application):
220 222 name = u'ipython-profile'
221 223 description = profile_help
222 224 examples = _main_examples
223 225
224 226 subcommands = Dict(dict(
225 227 create = (ProfileCreate, "Create a new profile dir with default config files"),
226 228 list = (ProfileList, "List existing profiles")
227 229 ))
228 230
229 231 def start(self):
230 232 if self.subapp is None:
231 233 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
232 234 print
233 235 self.print_description()
234 236 self.print_subcommands()
235 237 self.exit(1)
236 238 else:
237 239 return self.subapp.start()
238 240
@@ -1,442 +1,444 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 56 # Globals
57 57 #-----------------------------------------------------------------------------
58 58
59 59 _examples = """
60 60 ipython qtconsole # start the qtconsole
61 61 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
62 62 """
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Classes
66 66 #-----------------------------------------------------------------------------
67 67
68 68 class MainWindow(QtGui.QMainWindow):
69 69
70 70 #---------------------------------------------------------------------------
71 71 # 'object' interface
72 72 #---------------------------------------------------------------------------
73 73
74 74 def __init__(self, app, frontend, existing=False, may_close=True,
75 75 confirm_exit=True):
76 76 """ Create a MainWindow for the specified FrontendWidget.
77 77
78 78 The app is passed as an argument to allow for different
79 79 closing behavior depending on whether we are the Kernel's parent.
80 80
81 81 If existing is True, then this Console does not own the Kernel.
82 82
83 83 If may_close is True, then this Console is permitted to close the kernel
84 84 """
85 85 super(MainWindow, self).__init__()
86 86 self._app = app
87 87 self._frontend = frontend
88 88 self._existing = existing
89 89 if existing:
90 90 self._may_close = may_close
91 91 else:
92 92 self._may_close = True
93 93 self._frontend.exit_requested.connect(self.close)
94 94 self._confirm_exit = confirm_exit
95 95 self.setCentralWidget(frontend)
96 96
97 97 #---------------------------------------------------------------------------
98 98 # QWidget interface
99 99 #---------------------------------------------------------------------------
100 100
101 101 def closeEvent(self, event):
102 102 """ Close the window and the kernel (if necessary).
103 103
104 104 This will prompt the user if they are finished with the kernel, and if
105 105 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
106 106 it closes without prompt.
107 107 """
108 108 keepkernel = None #Use the prompt by default
109 109 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
110 110 keepkernel = self._frontend._keep_kernel_on_exit
111 111
112 112 kernel_manager = self._frontend.kernel_manager
113 113
114 114 if keepkernel is None and not self._confirm_exit:
115 115 # don't prompt, just terminate the kernel if we own it
116 116 # or leave it alone if we don't
117 117 keepkernel = not self._existing
118 118
119 119 if keepkernel is None: #show prompt
120 120 if kernel_manager and kernel_manager.channels_running:
121 121 title = self.window().windowTitle()
122 122 cancel = QtGui.QMessageBox.Cancel
123 123 okay = QtGui.QMessageBox.Ok
124 124 if self._may_close:
125 125 msg = "You are closing this Console window."
126 126 info = "Would you like to quit the Kernel and all attached Consoles as well?"
127 127 justthis = QtGui.QPushButton("&No, just this Console", self)
128 128 justthis.setShortcut('N')
129 129 closeall = QtGui.QPushButton("&Yes, quit everything", self)
130 130 closeall.setShortcut('Y')
131 131 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
132 132 title, msg)
133 133 box.setInformativeText(info)
134 134 box.addButton(cancel)
135 135 box.addButton(justthis, QtGui.QMessageBox.NoRole)
136 136 box.addButton(closeall, QtGui.QMessageBox.YesRole)
137 137 box.setDefaultButton(closeall)
138 138 box.setEscapeButton(cancel)
139 139 reply = box.exec_()
140 140 if reply == 1: # close All
141 141 kernel_manager.shutdown_kernel()
142 142 #kernel_manager.stop_channels()
143 143 event.accept()
144 144 elif reply == 0: # close Console
145 145 if not self._existing:
146 146 # Have kernel: don't quit, just close the window
147 147 self._app.setQuitOnLastWindowClosed(False)
148 148 self.deleteLater()
149 149 event.accept()
150 150 else:
151 151 event.ignore()
152 152 else:
153 153 reply = QtGui.QMessageBox.question(self, title,
154 154 "Are you sure you want to close this Console?"+
155 155 "\nThe Kernel and other Consoles will remain active.",
156 156 okay|cancel,
157 157 defaultButton=okay
158 158 )
159 159 if reply == okay:
160 160 event.accept()
161 161 else:
162 162 event.ignore()
163 163 elif keepkernel: #close console but leave kernel running (no prompt)
164 164 if kernel_manager and kernel_manager.channels_running:
165 165 if not self._existing:
166 166 # I have the kernel: don't quit, just close the window
167 167 self._app.setQuitOnLastWindowClosed(False)
168 168 event.accept()
169 169 else: #close console and kernel (no prompt)
170 170 if kernel_manager and kernel_manager.channels_running:
171 171 kernel_manager.shutdown_kernel()
172 172 event.accept()
173 173
174 174 #-----------------------------------------------------------------------------
175 175 # Aliases and Flags
176 176 #-----------------------------------------------------------------------------
177 177
178 178 flags = dict(ipkernel_flags)
179
180 flags.update({
179 qt_flags = {
181 180 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
182 181 "Connect to an existing kernel."),
183 182 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
184 183 "Use a pure Python kernel instead of an IPython kernel."),
185 184 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
186 185 "Disable rich text support."),
187 })
188 flags.update(boolean_flag(
186 }
187 qt_flags.update(boolean_flag(
189 188 'gui-completion', 'ConsoleWidget.gui_completion',
190 189 "use a GUI widget for tab completion",
191 190 "use plaintext output for completion"
192 191 ))
193 flags.update(boolean_flag(
192 qt_flags.update(boolean_flag(
194 193 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
195 194 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
196 195 to force a direct exit without any confirmation.
197 196 """,
198 197 """Don't prompt the user when exiting. This will terminate the kernel
199 198 if it is owned by the frontend, and leave it alive if it is external.
200 199 """
201 200 ))
201 flags.update(qt_flags)
202 202 # the flags that are specific to the frontend
203 203 # these must be scrubbed before being passed to the kernel,
204 204 # or it will raise an error on unrecognized flags
205 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
206 'confirm-exit', 'no-confirm-exit']
205 qt_flags = qt_flags.keys()
207 206
208 207 aliases = dict(ipkernel_aliases)
209 208
210 aliases.update(dict(
209 qt_aliases = dict(
211 210 hb = 'IPythonQtConsoleApp.hb_port',
212 211 shell = 'IPythonQtConsoleApp.shell_port',
213 212 iopub = 'IPythonQtConsoleApp.iopub_port',
214 213 stdin = 'IPythonQtConsoleApp.stdin_port',
215 214 ip = 'IPythonQtConsoleApp.ip',
216 215
217 plain = 'IPythonQtConsoleApp.plain',
218 pure = 'IPythonQtConsoleApp.pure',
219 216 style = 'IPythonWidget.syntax_style',
220 217 stylesheet = 'IPythonQtConsoleApp.stylesheet',
221 218 colors = 'ZMQInteractiveShell.colors',
222 219
223 220 editor = 'IPythonWidget.editor',
224 221 paging = 'ConsoleWidget.paging',
225 ))
226 aliases['gui-completion'] = 'ConsoleWidget.gui_completion'
222 )
223 aliases.update(qt_aliases)
224 # also scrub aliases from the frontend
225 qt_flags.extend(qt_aliases.keys())
227 226
228 227
229 228 #-----------------------------------------------------------------------------
230 229 # IPythonQtConsole
231 230 #-----------------------------------------------------------------------------
232 231
233 232
234 233 class IPythonQtConsoleApp(BaseIPythonApplication):
235 234 name = 'ipython-qtconsole'
236 235 default_config_file_name='ipython_config.py'
237 236
238 237 description = """
239 238 The IPython QtConsole.
240 239
241 240 This launches a Console-style application using Qt. It is not a full
242 241 console, in that launched terminal subprocesses will not be able to accept
243 242 input.
244 243
245 244 The QtConsole supports various extra features beyond the Terminal IPython
246 245 shell, such as inline plotting with matplotlib, via:
247 246
248 247 ipython qtconsole --pylab=inline
249 248
250 249 as well as saving your session as HTML, and printing the output.
251 250
252 251 """
253 252 examples = _examples
254 253
255 254 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
256 255 flags = Dict(flags)
257 256 aliases = Dict(aliases)
258 257
259 258 kernel_argv = List(Unicode)
260 259
261 260 # connection info:
262 261 ip = Unicode(LOCALHOST, config=True,
263 262 help="""Set the kernel\'s IP address [default localhost].
264 263 If the IP address is something other than localhost, then
265 264 Consoles on other machines will be able to connect
266 265 to the Kernel, so be careful!"""
267 266 )
268 267 hb_port = Int(0, config=True,
269 268 help="set the heartbeat port [default: random]")
270 269 shell_port = Int(0, config=True,
271 270 help="set the shell (XREP) port [default: random]")
272 271 iopub_port = Int(0, config=True,
273 272 help="set the iopub (PUB) port [default: random]")
274 273 stdin_port = Int(0, config=True,
275 274 help="set the stdin (XREQ) port [default: random]")
276 275
277 276 existing = CBool(False, config=True,
278 277 help="Whether to connect to an already running Kernel.")
279 278
280 279 stylesheet = Unicode('', config=True,
281 280 help="path to a custom CSS stylesheet")
282 281
283 282 pure = CBool(False, config=True,
284 283 help="Use a pure Python kernel instead of an IPython kernel.")
285 284 plain = CBool(False, config=True,
286 285 help="Use a plaintext widget instead of rich text (plain can't print/save).")
287 286
288 287 def _pure_changed(self, name, old, new):
289 288 kind = 'plain' if self.plain else 'rich'
290 289 self.config.ConsoleWidget.kind = kind
291 290 if self.pure:
292 291 self.widget_factory = FrontendWidget
293 292 elif self.plain:
294 293 self.widget_factory = IPythonWidget
295 294 else:
296 295 self.widget_factory = RichIPythonWidget
297 296
298 297 _plain_changed = _pure_changed
299 298
300 299 confirm_exit = CBool(True, config=True,
301 300 help="""
302 301 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
303 302 to force a direct exit without any confirmation.""",
304 303 )
305 304
306 305 # the factory for creating a widget
307 306 widget_factory = Any(RichIPythonWidget)
308 307
309 308 def parse_command_line(self, argv=None):
310 309 super(IPythonQtConsoleApp, self).parse_command_line(argv)
311 310 if argv is None:
312 311 argv = sys.argv[1:]
313 312
314 313 self.kernel_argv = list(argv) # copy
315 314 # kernel should inherit default config file from frontend
316 315 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
317 316 # scrub frontend-specific flags
318 317 for a in argv:
319 if a.startswith('-') and a.lstrip('-') in qt_flags:
320 self.kernel_argv.remove(a)
318
319 if a.startswith('-'):
320 key = a.lstrip('-').split('=')[0]
321 if key in qt_flags:
322 self.kernel_argv.remove(a)
321 323
322 324 def init_kernel_manager(self):
323 325 # Don't let Qt or ZMQ swallow KeyboardInterupts.
324 326 signal.signal(signal.SIGINT, signal.SIG_DFL)
325 327
326 328 # Create a KernelManager and start a kernel.
327 329 self.kernel_manager = QtKernelManager(
328 330 shell_address=(self.ip, self.shell_port),
329 331 sub_address=(self.ip, self.iopub_port),
330 332 stdin_address=(self.ip, self.stdin_port),
331 333 hb_address=(self.ip, self.hb_port),
332 334 config=self.config
333 335 )
334 336 # start the kernel
335 337 if not self.existing:
336 338 kwargs = dict(ip=self.ip, ipython=not self.pure)
337 339 kwargs['extra_arguments'] = self.kernel_argv
338 340 self.kernel_manager.start_kernel(**kwargs)
339 341 self.kernel_manager.start_channels()
340 342
341 343
342 344 def init_qt_elements(self):
343 345 # Create the widget.
344 346 self.app = QtGui.QApplication([])
345 347 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
346 348 self.widget = self.widget_factory(config=self.config,
347 349 local_kernel=local_kernel)
348 350 self.widget.kernel_manager = self.kernel_manager
349 351 self.window = MainWindow(self.app, self.widget, self.existing,
350 352 may_close=local_kernel,
351 353 confirm_exit=self.confirm_exit)
352 354 self.window.setWindowTitle('Python' if self.pure else 'IPython')
353 355
354 356 def init_colors(self):
355 357 """Configure the coloring of the widget"""
356 358 # Note: This will be dramatically simplified when colors
357 359 # are removed from the backend.
358 360
359 361 if self.pure:
360 362 # only IPythonWidget supports styling
361 363 return
362 364
363 365 # parse the colors arg down to current known labels
364 366 try:
365 367 colors = self.config.ZMQInteractiveShell.colors
366 368 except AttributeError:
367 369 colors = None
368 370 try:
369 371 style = self.config.IPythonWidget.colors
370 372 except AttributeError:
371 373 style = None
372 374
373 375 # find the value for colors:
374 376 if colors:
375 377 colors=colors.lower()
376 378 if colors in ('lightbg', 'light'):
377 379 colors='lightbg'
378 380 elif colors in ('dark', 'linux'):
379 381 colors='linux'
380 382 else:
381 383 colors='nocolor'
382 384 elif style:
383 385 if style=='bw':
384 386 colors='nocolor'
385 387 elif styles.dark_style(style):
386 388 colors='linux'
387 389 else:
388 390 colors='lightbg'
389 391 else:
390 392 colors=None
391 393
392 394 # Configure the style.
393 395 widget = self.widget
394 396 if style:
395 397 widget.style_sheet = styles.sheet_from_template(style, colors)
396 398 widget.syntax_style = style
397 399 widget._syntax_style_changed()
398 400 widget._style_sheet_changed()
399 401 elif colors:
400 402 # use a default style
401 403 widget.set_default_style(colors=colors)
402 404 else:
403 405 # this is redundant for now, but allows the widget's
404 406 # defaults to change
405 407 widget.set_default_style()
406 408
407 409 if self.stylesheet:
408 410 # we got an expicit stylesheet
409 411 if os.path.isfile(self.stylesheet):
410 412 with open(self.stylesheet) as f:
411 413 sheet = f.read()
412 414 widget.style_sheet = sheet
413 415 widget._style_sheet_changed()
414 416 else:
415 417 raise IOError("Stylesheet %r not found."%self.stylesheet)
416 418
417 419 def initialize(self, argv=None):
418 420 super(IPythonQtConsoleApp, self).initialize(argv)
419 421 self.init_kernel_manager()
420 422 self.init_qt_elements()
421 423 self.init_colors()
422 424
423 425 def start(self):
424 426
425 427 # draw the window
426 428 self.window.show()
427 429
428 430 # Start the application main loop.
429 431 self.app.exec_()
430 432
431 433 #-----------------------------------------------------------------------------
432 434 # Main entry point
433 435 #-----------------------------------------------------------------------------
434 436
435 437 def main():
436 438 app = IPythonQtConsoleApp()
437 439 app.initialize()
438 440 app.start()
439 441
440 442
441 443 if __name__ == '__main__':
442 444 main()
@@ -1,484 +1,529 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 from IPython.utils.sysinfo import num_cpus
40 41 from IPython.utils.traitlets import (Int, Unicode, Bool, CFloat, Dict, List,
41 42 DottedObjectName)
42 43
43 44 from IPython.parallel.apps.baseapp import (
44 45 BaseParallelApplication,
45 46 PIDFileError,
46 47 base_flags, base_aliases
47 48 )
48 49
49 50
50 51 #-----------------------------------------------------------------------------
51 52 # Module level variables
52 53 #-----------------------------------------------------------------------------
53 54
54 55
55 56 default_config_file_name = u'ipcluster_config.py'
56 57
57 58
58 59 _description = """Start an IPython cluster for parallel computing.
59 60
60 61 An IPython cluster consists of 1 controller and 1 or more engines.
61 62 This command automates the startup of these processes using a wide
62 63 range of startup methods (SSH, local processes, PBS, mpiexec,
63 64 Windows HPC Server 2008). To start a cluster with 4 engines on your
64 65 local host simply do 'ipcluster start --n=4'. For more complex usage
65 66 you will typically do 'ipython create mycluster --parallel', then edit
66 67 configuration files, followed by 'ipcluster start --profile=mycluster --n=4'.
67 68 """
68 69
69 70 _main_examples = """
70 71 ipcluster start --n=4 # start a 4 node cluster on localhost
71 72 ipcluster start -h # show the help string for the start subcmd
72 73
73 74 ipcluster stop -h # show the help string for the stop subcmd
74 75 ipcluster engines -h # show the help string for the engines subcmd
75 76 """
76 77
77 78 _start_examples = """
78 79 ipython profile create mycluster --parallel # create mycluster profile
79 80 ipcluster start --profile=mycluster --n=4 # start mycluster with 4 nodes
80 81 """
81 82
82 83 _stop_examples = """
83 84 ipcluster stop --profile=mycluster # stop a running cluster by profile name
84 85 """
85 86
86 87 _engines_examples = """
87 88 ipcluster engines --profile=mycluster --n=4 # start 4 engines only
88 89 """
89 90
90 91
91 92 # Exit codes for ipcluster
92 93
93 94 # This will be the exit code if the ipcluster appears to be running because
94 95 # a .pid file exists
95 96 ALREADY_STARTED = 10
96 97
97 98
98 99 # This will be the exit code if ipcluster stop is run, but there is not .pid
99 100 # file to be found.
100 101 ALREADY_STOPPED = 11
101 102
102 103 # This will be the exit code if ipcluster engines is run, but there is not .pid
103 104 # file to be found.
104 105 NO_CLUSTER = 12
105 106
106 107
107 108 #-----------------------------------------------------------------------------
108 109 # Main application
109 110 #-----------------------------------------------------------------------------
110 111 start_help = """Start an IPython cluster for parallel computing
111 112
112 113 Start an ipython cluster by its profile name or cluster
113 114 directory. Cluster directories contain configuration, log and
114 115 security related files and are named using the convention
115 116 'profile_<name>' and should be creating using the 'start'
116 117 subcommand of 'ipcluster'. If your cluster directory is in
117 118 the cwd or the ipython directory, you can simply refer to it
118 119 using its profile name, 'ipcluster start --n=4 --profile=<profile>`,
119 120 otherwise use the 'profile-dir' option.
120 121 """
121 122 stop_help = """Stop a running IPython cluster
122 123
123 124 Stop a running ipython cluster by its profile name or cluster
124 125 directory. Cluster directories are named using the convention
125 126 'profile_<name>'. If your cluster directory is in
126 127 the cwd or the ipython directory, you can simply refer to it
127 128 using its profile name, 'ipcluster stop --profile=<profile>`, otherwise
128 129 use the '--profile-dir' option.
129 130 """
130 131 engines_help = """Start engines connected to an existing IPython cluster
131 132
132 133 Start one or more engines to connect to an existing Cluster
133 134 by profile name or cluster directory.
134 135 Cluster directories contain configuration, log and
135 136 security related files and are named using the convention
136 137 'profile_<name>' and should be creating using the 'start'
137 138 subcommand of 'ipcluster'. If your cluster directory is in
138 139 the cwd or the ipython directory, you can simply refer to it
139 140 using its profile name, 'ipcluster engines --n=4 --profile=<profile>`,
140 141 otherwise use the 'profile-dir' option.
141 142 """
142 143 stop_aliases = dict(
143 144 signal='IPClusterStop.signal',
144 145 )
145 146 stop_aliases.update(base_aliases)
146 147
147 148 class IPClusterStop(BaseParallelApplication):
148 149 name = u'ipcluster'
149 150 description = stop_help
150 151 examples = _stop_examples
151 152 config_file_name = Unicode(default_config_file_name)
152 153
153 154 signal = Int(signal.SIGINT, config=True,
154 155 help="signal to use for stopping processes.")
155 156
156 157 aliases = Dict(stop_aliases)
157 158
158 159 def start(self):
159 160 """Start the app for the stop subcommand."""
160 161 try:
161 162 pid = self.get_pid_from_file()
162 163 except PIDFileError:
163 164 self.log.critical(
164 165 'Could not read pid file, cluster is probably not running.'
165 166 )
166 167 # Here I exit with a unusual exit status that other processes
167 168 # can watch for to learn how I existed.
168 169 self.remove_pid_file()
169 170 self.exit(ALREADY_STOPPED)
170 171
171 172 if not self.check_pid(pid):
172 173 self.log.critical(
173 174 'Cluster [pid=%r] is not running.' % pid
174 175 )
175 176 self.remove_pid_file()
176 177 # Here I exit with a unusual exit status that other processes
177 178 # can watch for to learn how I existed.
178 179 self.exit(ALREADY_STOPPED)
179 180
180 181 elif os.name=='posix':
181 182 sig = self.signal
182 183 self.log.info(
183 184 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
184 185 )
185 186 try:
186 187 os.kill(pid, sig)
187 188 except OSError:
188 189 self.log.error("Stopping cluster failed, assuming already dead.",
189 190 exc_info=True)
190 191 self.remove_pid_file()
191 192 elif os.name=='nt':
192 193 try:
193 194 # kill the whole tree
194 195 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
195 196 except (CalledProcessError, OSError):
196 197 self.log.error("Stopping cluster failed, assuming already dead.",
197 198 exc_info=True)
198 199 self.remove_pid_file()
199 200
200 201 engine_aliases = {}
201 202 engine_aliases.update(base_aliases)
202 203 engine_aliases.update(dict(
203 204 n='IPClusterEngines.n',
204 205 engines = 'IPClusterEngines.engine_launcher_class',
205 206 daemonize = 'IPClusterEngines.daemonize',
206 207 ))
207 208 engine_flags = {}
208 209 engine_flags.update(base_flags)
209 210
210 211 engine_flags.update(dict(
211 212 daemonize=(
212 213 {'IPClusterEngines' : {'daemonize' : True}},
213 214 """run the cluster into the background (not available on Windows)""",
214 215 )
215 216 ))
216 217 class IPClusterEngines(BaseParallelApplication):
217 218
218 219 name = u'ipcluster'
219 220 description = engines_help
220 221 examples = _engines_examples
221 222 usage = None
222 223 config_file_name = Unicode(default_config_file_name)
223 224 default_log_level = logging.INFO
224 225 classes = List()
225 226 def _classes_default(self):
226 227 from IPython.parallel.apps import launcher
227 228 launchers = launcher.all_launchers
228 229 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
229 230 return [ProfileDir]+eslaunchers
230 231
231 n = Int(2, config=True,
232 help="The number of engines to start.")
232 n = Int(num_cpus(), config=True,
233 help="""The number of engines to start. The default is to use one for each
234 CPU on your machine""")
233 235
234 236 engine_launcher_class = DottedObjectName('LocalEngineSetLauncher',
235 237 config=True,
236 help="The class for launching a set of Engines."
238 help="""The class for launching a set of Engines. Change this value
239 to use various batch systems to launch your engines, such as PBS,SGE,MPIExec,etc.
240 Each launcher class has its own set of configuration options, for making sure
241 it will work in your environment.
242
243 You can also write your own launcher, and specify it's absolute import path,
244 as in 'mymodule.launcher.FTLEnginesLauncher`.
245
246 Examples include:
247
248 LocalEngineSetLauncher : start engines locally as subprocesses [default]
249 MPIExecEngineSetLauncher : use mpiexec to launch in an MPI environment
250 PBSEngineSetLauncher : use PBS (qsub) to submit engines to a batch queue
251 SGEEngineSetLauncher : use SGE (qsub) to submit engines to a batch queue
252 SSHEngineSetLauncher : use SSH to start the controller
253 Note that SSH does *not* move the connection files
254 around, so you will likely have to do this manually
255 unless the machines are on a shared file system.
256 WindowsHPCEngineSetLauncher : use Windows HPC
257 """
237 258 )
238 259 daemonize = Bool(False, config=True,
239 260 help="""Daemonize the ipcluster program. This implies --log-to-file.
240 261 Not available on Windows.
241 262 """)
242 263
243 264 def _daemonize_changed(self, name, old, new):
244 265 if new:
245 266 self.log_to_file = True
246 267
247 268 aliases = Dict(engine_aliases)
248 269 flags = Dict(engine_flags)
249 270 _stopping = False
250 271
251 272 def initialize(self, argv=None):
252 273 super(IPClusterEngines, self).initialize(argv)
253 274 self.init_signal()
254 275 self.init_launchers()
255 276
256 277 def init_launchers(self):
257 278 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
258 279 self.engine_launcher.on_stop(lambda r: self.loop.stop())
259 280
260 281 def init_signal(self):
261 282 # Setup signals
262 283 signal.signal(signal.SIGINT, self.sigint_handler)
263 284
264 285 def build_launcher(self, clsname):
265 286 """import and instantiate a Launcher based on importstring"""
266 287 if '.' not in clsname:
267 288 # not a module, presume it's the raw name in apps.launcher
268 289 clsname = 'IPython.parallel.apps.launcher.'+clsname
269 290 # print repr(clsname)
270 klass = import_item(clsname)
291 try:
292 klass = import_item(clsname)
293 except (ImportError, KeyError):
294 self.log.fatal("Could not import launcher class: %r"%clsname)
295 self.exit(1)
271 296
272 297 launcher = klass(
273 work_dir=self.profile_dir.location, config=self.config, log=self.log
298 work_dir=u'.', config=self.config, log=self.log
274 299 )
275 300 return launcher
276 301
277 302 def start_engines(self):
278 303 self.log.info("Starting %i engines"%self.n)
279 304 self.engine_launcher.start(
280 305 self.n,
281 306 self.profile_dir.location
282 307 )
283 308
284 309 def stop_engines(self):
285 310 self.log.info("Stopping Engines...")
286 311 if self.engine_launcher.running:
287 312 d = self.engine_launcher.stop()
288 313 return d
289 314 else:
290 315 return None
291 316
292 317 def stop_launchers(self, r=None):
293 318 if not self._stopping:
294 319 self._stopping = True
295 320 self.log.error("IPython cluster: stopping")
296 321 self.stop_engines()
297 322 # Wait a few seconds to let things shut down.
298 323 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
299 324 dc.start()
300 325
301 326 def sigint_handler(self, signum, frame):
302 327 self.log.debug("SIGINT received, stopping launchers...")
303 328 self.stop_launchers()
304 329
305 330 def start_logging(self):
306 331 # Remove old log files of the controller and engine
307 332 if self.clean_logs:
308 333 log_dir = self.profile_dir.log_dir
309 334 for f in os.listdir(log_dir):
310 335 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
311 336 os.remove(os.path.join(log_dir, f))
312 337 # This will remove old log files for ipcluster itself
313 338 # super(IPBaseParallelApplication, self).start_logging()
314 339
315 340 def start(self):
316 341 """Start the app for the engines subcommand."""
317 342 self.log.info("IPython cluster: started")
318 343 # First see if the cluster is already running
319 344
320 345 # Now log and daemonize
321 346 self.log.info(
322 347 'Starting engines with [daemon=%r]' % self.daemonize
323 348 )
324 349 # TODO: Get daemonize working on Windows or as a Windows Server.
325 350 if self.daemonize:
326 351 if os.name=='posix':
327 352 daemonize()
328 353
329 354 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
330 355 dc.start()
331 356 # Now write the new pid file AFTER our new forked pid is active.
332 357 # self.write_pid_file()
333 358 try:
334 359 self.loop.start()
335 360 except KeyboardInterrupt:
336 361 pass
337 362 except zmq.ZMQError as e:
338 363 if e.errno == errno.EINTR:
339 364 pass
340 365 else:
341 366 raise
342 367
343 368 start_aliases = {}
344 369 start_aliases.update(engine_aliases)
345 370 start_aliases.update(dict(
346 371 delay='IPClusterStart.delay',
347 372 controller = 'IPClusterStart.controller_launcher_class',
348 373 ))
349 374 start_aliases['clean-logs'] = 'IPClusterStart.clean_logs'
350 375
376 # set inherited Start keys directly, to ensure command-line args get higher priority
377 # than config file options.
378 for key,value in start_aliases.items():
379 if value.startswith('IPClusterEngines'):
380 start_aliases[key] = value.replace('IPClusterEngines', 'IPClusterStart')
381
351 382 class IPClusterStart(IPClusterEngines):
352 383
353 384 name = u'ipcluster'
354 385 description = start_help
355 386 examples = _start_examples
356 387 default_log_level = logging.INFO
357 388 auto_create = Bool(True, config=True,
358 389 help="whether to create the profile_dir if it doesn't exist")
359 390 classes = List()
360 391 def _classes_default(self,):
361 392 from IPython.parallel.apps import launcher
362 393 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
363 394
364 395 clean_logs = Bool(True, config=True,
365 396 help="whether to cleanup old logs before starting")
366 397
367 398 delay = CFloat(1., config=True,
368 399 help="delay (in s) between starting the controller and the engines")
369 400
370 401 controller_launcher_class = DottedObjectName('LocalControllerLauncher',
371 402 config=True,
372 help="The class for launching a Controller."
403 helep="""The class for launching a Controller. Change this value if you want
404 your controller to also be launched by a batch system, such as PBS,SGE,MPIExec,etc.
405
406 Each launcher class has its own set of configuration options, for making sure
407 it will work in your environment.
408
409 Examples include:
410
411 LocalControllerLauncher : start engines locally as subprocesses
412 MPIExecControllerLauncher : use mpiexec to launch engines in an MPI universe
413 PBSControllerLauncher : use PBS (qsub) to submit engines to a batch queue
414 SGEControllerLauncher : use SGE (qsub) to submit engines to a batch queue
415 SSHControllerLauncher : use SSH to start the controller
416 WindowsHPCControllerLauncher : use Windows HPC
417 """
373 418 )
374 419 reset = Bool(False, config=True,
375 420 help="Whether to reset config files as part of '--create'."
376 421 )
377 422
378 423 # flags = Dict(flags)
379 424 aliases = Dict(start_aliases)
380 425
381 426 def init_launchers(self):
382 427 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
383 428 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
384 429 self.controller_launcher.on_stop(self.stop_launchers)
385 430
386 431 def start_controller(self):
387 432 self.controller_launcher.start(
388 433 self.profile_dir.location
389 434 )
390 435
391 436 def stop_controller(self):
392 437 # self.log.info("In stop_controller")
393 438 if self.controller_launcher and self.controller_launcher.running:
394 439 return self.controller_launcher.stop()
395 440
396 441 def stop_launchers(self, r=None):
397 442 if not self._stopping:
398 443 self.stop_controller()
399 444 super(IPClusterStart, self).stop_launchers()
400 445
401 446 def start(self):
402 447 """Start the app for the start subcommand."""
403 448 # First see if the cluster is already running
404 449 try:
405 450 pid = self.get_pid_from_file()
406 451 except PIDFileError:
407 452 pass
408 453 else:
409 454 if self.check_pid(pid):
410 455 self.log.critical(
411 456 'Cluster is already running with [pid=%s]. '
412 457 'use "ipcluster stop" to stop the cluster.' % pid
413 458 )
414 459 # Here I exit with a unusual exit status that other processes
415 460 # can watch for to learn how I existed.
416 461 self.exit(ALREADY_STARTED)
417 462 else:
418 463 self.remove_pid_file()
419 464
420 465
421 466 # Now log and daemonize
422 467 self.log.info(
423 468 'Starting ipcluster with [daemon=%r]' % self.daemonize
424 469 )
425 470 # TODO: Get daemonize working on Windows or as a Windows Server.
426 471 if self.daemonize:
427 472 if os.name=='posix':
428 473 daemonize()
429 474
430 475 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
431 476 dc.start()
432 477 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
433 478 dc.start()
434 479 # Now write the new pid file AFTER our new forked pid is active.
435 480 self.write_pid_file()
436 481 try:
437 482 self.loop.start()
438 483 except KeyboardInterrupt:
439 484 pass
440 485 except zmq.ZMQError as e:
441 486 if e.errno == errno.EINTR:
442 487 pass
443 488 else:
444 489 raise
445 490 finally:
446 491 self.remove_pid_file()
447 492
448 493 base='IPython.parallel.apps.ipclusterapp.IPCluster'
449 494
450 495 class IPClusterApp(Application):
451 496 name = u'ipcluster'
452 497 description = _description
453 498 examples = _main_examples
454 499
455 500 subcommands = {
456 501 'start' : (base+'Start', start_help),
457 502 'stop' : (base+'Stop', stop_help),
458 503 'engines' : (base+'Engines', engines_help),
459 504 }
460 505
461 506 # no aliases or flags for parent App
462 507 aliases = Dict()
463 508 flags = Dict()
464 509
465 510 def start(self):
466 511 if self.subapp is None:
467 512 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
468 513 print
469 514 self.print_description()
470 515 self.print_subcommands()
471 516 self.exit(1)
472 517 else:
473 518 return self.subapp.start()
474 519
475 520 def launch_new_instance():
476 521 """Create and run the IPython cluster."""
477 522 app = IPClusterApp.instance()
478 523 app.initialize()
479 524 app.start()
480 525
481 526
482 527 if __name__ == '__main__':
483 528 launch_new_instance()
484 529
General Comments 0
You need to be logged in to leave comments. Login now