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