##// END OF EJS Templates
Merge branch 'newapp'...
MinRK -
r4042:fbd5ae94 merge
parent child Browse files
Show More
@@ -0,0 +1,5 b''
1 This is the IPython directory.
2
3 For more information on configuring IPython, do:
4
5 ipython config -h
@@ -0,0 +1,219 b''
1 # encoding: utf-8
2 """
3 An application for managing IPython profiles.
4
5 To be invoked as the `ipython profile` subcommand.
6
7 Authors:
8
9 * Min RK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 import logging
25 import os
26
27 from IPython.config.application import Application, boolean_flag
28 from IPython.core.application import (
29 BaseIPythonApplication, base_flags, base_aliases
30 )
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import get_ipython_dir
33 from IPython.utils.traitlets import Unicode, Bool, Dict
34
35 #-----------------------------------------------------------------------------
36 # Constants
37 #-----------------------------------------------------------------------------
38
39 create_help = """Create an IPython profile by name
40
41 Create an ipython profile directory by its name or
42 profile directory path. Profile directories contain
43 configuration, log and security related files and are named
44 using the convention 'profile_<name>'. By default they are
45 located in your ipython directory. Once created, you will
46 can edit the configuration files in the profile
47 directory to configure IPython. Most users will create a
48 profile directory by name,
49 `ipython profile create myprofile`, which will put the directory
50 in `<ipython_dir>/profile_myprofile`.
51 """
52 list_help = """List available IPython profiles
53
54 List all available profiles, by profile location, that can
55 be found in the current working directly or in the ipython
56 directory. Profile directories are named using the convention
57 'profile_<profile>'.
58 """
59 profile_help = """Manage IPython profiles
60
61 Profile directories contain
62 configuration, log and security related files and are named
63 using the convention 'profile_<name>'. By default they are
64 located in your ipython directory. You can create profiles
65 with `ipython profile create <name>`, or see the profiles you
66 already have with `ipython profile list`
67
68 To get started configuring IPython, simply do:
69
70 $> ipython profile create
71
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.
74
75 """
76
77 #-----------------------------------------------------------------------------
78 # Profile Application Class (for `ipython profile` subcommand)
79 #-----------------------------------------------------------------------------
80
81
82
83 class ProfileList(Application):
84 name = u'ipython-profile'
85 description = list_help
86
87 aliases = Dict(dict(
88 ipython_dir = 'ProfileList.ipython_dir',
89 log_level = 'Application.log_level',
90 ))
91 flags = Dict(dict(
92 debug = ({'Application' : {'log_level' : 0}},
93 "Set log_level to 0, maximizing log output."
94 )
95 ))
96 ipython_dir = Unicode(get_ipython_dir(), config=True,
97 help="""
98 The name of the IPython directory. This directory is used for logging
99 configuration (through profiles), history storage, etc. The default
100 is usually $HOME/.ipython. This options can also be specified through
101 the environment variable IPYTHON_DIR.
102 """
103 )
104
105 def list_profile_dirs(self):
106 # Find the search paths
107 paths = [os.getcwdu(), self.ipython_dir]
108
109 self.log.warn('Searching for IPython profiles in paths: %r' % paths)
110 for path in paths:
111 files = os.listdir(path)
112 for f in files:
113 full_path = os.path.join(path, f)
114 if os.path.isdir(full_path) and f.startswith('profile_'):
115 profile = f.split('_',1)[-1]
116 start_cmd = 'ipython profile=%s' % profile
117 print start_cmd + " ==> " + full_path
118
119 def start(self):
120 self.list_profile_dirs()
121
122
123 create_flags = {}
124 create_flags.update(base_flags)
125 create_flags.update(boolean_flag('reset', 'ProfileCreate.overwrite',
126 "reset config files to defaults", "leave existing config files"))
127 create_flags.update(boolean_flag('parallel', 'ProfileCreate.parallel',
128 "Include parallel computing config files",
129 "Don't include parallel computing config files"))
130
131 class ProfileCreate(BaseIPythonApplication):
132 name = u'ipython-profile'
133 description = create_help
134 auto_create = Bool(True, config=False)
135
136 def _copy_config_files_default(self):
137 return True
138
139 parallel = Bool(False, config=True,
140 help="whether to include parallel computing config files")
141 def _parallel_changed(self, name, old, new):
142 parallel_files = [ 'ipcontroller_config.py',
143 'ipengine_config.py',
144 'ipcluster_config.py'
145 ]
146 if new:
147 for cf in parallel_files:
148 self.config_files.append(cf)
149 else:
150 for cf in parallel_files:
151 if cf in self.config_files:
152 self.config_files.remove(cf)
153
154 def parse_command_line(self, argv):
155 super(ProfileCreate, self).parse_command_line(argv)
156 # accept positional arg as profile name
157 if self.extra_args:
158 self.profile = self.extra_args[0]
159
160 flags = Dict(create_flags)
161
162 aliases = Dict(dict(profile='BaseIPythonApplication.profile'))
163
164 classes = [ProfileDir]
165
166 def init_config_files(self):
167 super(ProfileCreate, self).init_config_files()
168 # use local imports, since these classes may import from here
169 from IPython.frontend.terminal.ipapp import TerminalIPythonApp
170 apps = [TerminalIPythonApp]
171 try:
172 from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
173 except ImportError:
174 pass
175 else:
176 apps.append(IPythonQtConsoleApp)
177 if self.parallel:
178 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
179 from IPython.parallel.apps.ipengineapp import IPEngineApp
180 from IPython.parallel.apps.ipclusterapp import IPClusterStart
181 from IPython.parallel.apps.iploggerapp import IPLoggerApp
182 apps.extend([
183 IPControllerApp,
184 IPEngineApp,
185 IPClusterStart,
186 IPLoggerApp,
187 ])
188 for App in apps:
189 app = App()
190 app.config.update(self.config)
191 app.log = self.log
192 app.overwrite = self.overwrite
193 app.copy_config_files=True
194 app.profile = self.profile
195 app.init_profile_dir()
196 app.init_config_files()
197
198 def stage_default_config_file(self):
199 pass
200
201 class ProfileApp(Application):
202 name = u'ipython-profile'
203 description = profile_help
204
205 subcommands = Dict(dict(
206 create = (ProfileCreate, "Create a new profile dir with default config files"),
207 list = (ProfileList, "List existing profiles")
208 ))
209
210 def start(self):
211 if self.subapp is None:
212 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
213 print
214 self.print_description()
215 self.print_subcommands()
216 self.exit(1)
217 else:
218 return self.subapp.start()
219
@@ -0,0 +1,206 b''
1 # encoding: utf-8
2 """
3 An object for managing IPython profile directories.
4
5 Authors:
6
7 * Brian Granger
8 * Fernando Perez
9 * Min RK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 import os
25 import shutil
26 import sys
27
28 from IPython.config.configurable import Configurable
29 from IPython.config.loader import Config
30 from IPython.utils.path import get_ipython_package_dir, expand_path
31 from IPython.utils.traitlets import List, Unicode, Bool
32
33 #-----------------------------------------------------------------------------
34 # Classes and functions
35 #-----------------------------------------------------------------------------
36
37
38 #-----------------------------------------------------------------------------
39 # Module errors
40 #-----------------------------------------------------------------------------
41
42 class ProfileDirError(Exception):
43 pass
44
45
46 #-----------------------------------------------------------------------------
47 # Class for managing profile directories
48 #-----------------------------------------------------------------------------
49
50 class ProfileDir(Configurable):
51 """An object to manage the profile directory and its resources.
52
53 The profile directory is used by all IPython applications, to manage
54 configuration, logging and security.
55
56 This object knows how to find, create and manage these directories. This
57 should be used by any code that wants to handle profiles.
58 """
59
60 security_dir_name = Unicode('security')
61 log_dir_name = Unicode('log')
62 pid_dir_name = Unicode('pid')
63 security_dir = Unicode(u'')
64 log_dir = Unicode(u'')
65 pid_dir = Unicode(u'')
66
67 location = Unicode(u'', config=True,
68 help="""Set the profile location directly. This overrides the logic used by the
69 `profile` option.""",
70 )
71
72 _location_isset = Bool(False) # flag for detecting multiply set location
73
74 def _location_changed(self, name, old, new):
75 if self._location_isset:
76 raise RuntimeError("Cannot set profile location more than once.")
77 self._location_isset = True
78 if not os.path.isdir(new):
79 os.makedirs(new)
80
81 # ensure config files exist:
82 self.security_dir = os.path.join(new, self.security_dir_name)
83 self.log_dir = os.path.join(new, self.log_dir_name)
84 self.pid_dir = os.path.join(new, self.pid_dir_name)
85 self.check_dirs()
86
87 def _log_dir_changed(self, name, old, new):
88 self.check_log_dir()
89
90 def check_log_dir(self):
91 if not os.path.isdir(self.log_dir):
92 os.mkdir(self.log_dir)
93
94 def _security_dir_changed(self, name, old, new):
95 self.check_security_dir()
96
97 def check_security_dir(self):
98 if not os.path.isdir(self.security_dir):
99 os.mkdir(self.security_dir, 0700)
100 else:
101 os.chmod(self.security_dir, 0700)
102
103 def _pid_dir_changed(self, name, old, new):
104 self.check_pid_dir()
105
106 def check_pid_dir(self):
107 if not os.path.isdir(self.pid_dir):
108 os.mkdir(self.pid_dir, 0700)
109 else:
110 os.chmod(self.pid_dir, 0700)
111
112 def check_dirs(self):
113 self.check_security_dir()
114 self.check_log_dir()
115 self.check_pid_dir()
116
117 def copy_config_file(self, config_file, path=None, overwrite=False):
118 """Copy a default config file into the active profile directory.
119
120 Default configuration files are kept in :mod:`IPython.config.default`.
121 This function moves these from that location to the working profile
122 directory.
123 """
124 dst = os.path.join(self.location, config_file)
125 if os.path.isfile(dst) and not overwrite:
126 return
127 if path is None:
128 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
129 src = os.path.join(path, config_file)
130 shutil.copy(src, dst)
131
132 @classmethod
133 def create_profile_dir(cls, profile_dir, config=None):
134 """Create a new profile directory given a full path.
135
136 Parameters
137 ----------
138 profile_dir : str
139 The full path to the profile directory. If it does exist, it will
140 be used. If not, it will be created.
141 """
142 return cls(location=profile_dir, config=config)
143
144 @classmethod
145 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
146 """Create a profile dir by profile name and path.
147
148 Parameters
149 ----------
150 path : unicode
151 The path (directory) to put the profile directory in.
152 name : unicode
153 The name of the profile. The name of the profile directory will
154 be "profile_<profile>".
155 """
156 if not os.path.isdir(path):
157 raise ProfileDirError('Directory not found: %s' % path)
158 profile_dir = os.path.join(path, u'profile_' + name)
159 return cls(location=profile_dir, config=config)
160
161 @classmethod
162 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
163 """Find an existing profile dir by profile name, return its ProfileDir.
164
165 This searches through a sequence of paths for a profile dir. If it
166 is not found, a :class:`ProfileDirError` exception will be raised.
167
168 The search path algorithm is:
169 1. ``os.getcwd()``
170 2. ``ipython_dir``
171
172 Parameters
173 ----------
174 ipython_dir : unicode or str
175 The IPython directory to use.
176 name : unicode or str
177 The name of the profile. The name of the profile directory
178 will be "profile_<profile>".
179 """
180 dirname = u'profile_' + name
181 paths = [os.getcwdu(), ipython_dir]
182 for p in paths:
183 profile_dir = os.path.join(p, dirname)
184 if os.path.isdir(profile_dir):
185 return cls(location=profile_dir, config=config)
186 else:
187 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
188
189 @classmethod
190 def find_profile_dir(cls, profile_dir, config=None):
191 """Find/create a profile dir and return its ProfileDir.
192
193 This will create the profile directory if it doesn't exist.
194
195 Parameters
196 ----------
197 profile_dir : unicode or str
198 The path of the profile directory. This is expanded using
199 :func:`IPython.utils.genutils.expand_path`.
200 """
201 profile_dir = expand_path(profile_dir)
202 if not os.path.isdir(profile_dir):
203 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
204 return cls(location=profile_dir, config=config)
205
206
@@ -0,0 +1,244 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 A mixin for :class:`~IPython.core.application.Application` classes that
5 launch InteractiveShell instances, load extensions, etc.
6
7 Authors
8 -------
9
10 * Min Ragan-Kelley
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 from __future__ import absolute_import
25
26 import os
27 import sys
28
29 from IPython.config.application import boolean_flag
30 from IPython.config.configurable import Configurable
31 from IPython.config.loader import Config
32 from IPython.utils.path import filefind
33 from IPython.utils.traitlets import Unicode, Instance, List
34
35 #-----------------------------------------------------------------------------
36 # Aliases and Flags
37 #-----------------------------------------------------------------------------
38
39 shell_flags = {}
40
41 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
42 addflag('autoindent', 'InteractiveShell.autoindent',
43 'Turn on autoindenting.', 'Turn off autoindenting.'
44 )
45 addflag('automagic', 'InteractiveShell.automagic',
46 """Turn on the auto calling of magic commands. Type %%magic at the
47 IPython prompt for more information.""",
48 'Turn off the auto calling of magic commands.'
49 )
50 addflag('pdb', 'InteractiveShell.pdb',
51 "Enable auto calling the pdb debugger after every exception.",
52 "Disable auto calling the pdb debugger after every exception."
53 )
54 addflag('pprint', 'PlainTextFormatter.pprint',
55 "Enable auto pretty printing of results.",
56 "Disable auto auto pretty printing of results."
57 )
58 addflag('color-info', 'InteractiveShell.color_info',
59 """IPython can display information about objects via a set of func-
60 tions, and optionally can use colors for this, syntax highlighting
61 source code and various other elements. However, because this
62 information is passed through a pager (like 'less') and many pagers get
63 confused with color codes, this option is off by default. You can test
64 it and turn it on permanently in your ipython_config.py file if it
65 works for you. Test it and turn it on permanently if it works with
66 your system. The magic function %%color_info allows you to toggle this
67 interactively for testing.""",
68 "Disable using colors for info related things."
69 )
70 addflag('deep-reload', 'InteractiveShell.deep_reload',
71 """Enable deep (recursive) reloading by default. IPython can use the
72 deep_reload module which reloads changes in modules recursively (it
73 replaces the reload() function, so you don't need to change anything to
74 use it). deep_reload() forces a full reload of modules whose code may
75 have changed, which the default reload() function does not. When
76 deep_reload is off, IPython will use the normal reload(), but
77 deep_reload will still be available as dreload(). This feature is off
78 by default [which means that you have both normal reload() and
79 dreload()].""",
80 "Disable deep (recursive) reloading by default."
81 )
82 nosep_config = Config()
83 nosep_config.InteractiveShell.separate_in = ''
84 nosep_config.InteractiveShell.separate_out = ''
85 nosep_config.InteractiveShell.separate_out2 = ''
86
87 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
88
89
90 # it's possible we don't want short aliases for *all* of these:
91 shell_aliases = dict(
92 autocall='InteractiveShell.autocall',
93 cache_size='InteractiveShell.cache_size',
94 colors='InteractiveShell.colors',
95 logfile='InteractiveShell.logfile',
96 log_append='InteractiveShell.logappend',
97 c='InteractiveShellApp.code_to_run',
98 ext='InteractiveShellApp.extra_extension',
99 )
100
101 #-----------------------------------------------------------------------------
102 # Main classes and functions
103 #-----------------------------------------------------------------------------
104
105 class InteractiveShellApp(Configurable):
106 """A Mixin for applications that start InteractiveShell instances.
107
108 Provides configurables for loading extensions and executing files
109 as part of configuring a Shell environment.
110
111 Provides init_extensions() and init_code() methods, to be called
112 after init_shell(), which must be implemented by subclasses.
113 """
114 extensions = List(Unicode, config=True,
115 help="A list of dotted module names of IPython extensions to load."
116 )
117 extra_extension = Unicode('', config=True,
118 help="dotted module name of an IPython extension to load."
119 )
120 def _extra_extension_changed(self, name, old, new):
121 if new:
122 # add to self.extensions
123 self.extensions.append(new)
124
125 exec_files = List(Unicode, config=True,
126 help="""List of files to run at IPython startup."""
127 )
128 file_to_run = Unicode('', config=True,
129 help="""A file to be run""")
130
131 exec_lines = List(Unicode, config=True,
132 help="""lines of code to run at IPython startup."""
133 )
134 code_to_run = Unicode('', config=True,
135 help="Execute the given command string."
136 )
137 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
138
139 def init_shell(self):
140 raise NotImplementedError("Override in subclasses")
141
142 def init_extensions(self):
143 """Load all IPython extensions in IPythonApp.extensions.
144
145 This uses the :meth:`ExtensionManager.load_extensions` to load all
146 the extensions listed in ``self.extensions``.
147 """
148 if not self.extensions:
149 return
150 try:
151 self.log.debug("Loading IPython extensions...")
152 extensions = self.extensions
153 for ext in extensions:
154 try:
155 self.log.info("Loading IPython extension: %s" % ext)
156 self.shell.extension_manager.load_extension(ext)
157 except:
158 self.log.warn("Error in loading extension: %s" % ext)
159 self.shell.showtraceback()
160 except:
161 self.log.warn("Unknown error in loading extensions:")
162 self.shell.showtraceback()
163
164 def init_code(self):
165 """run the pre-flight code, specified via exec_lines"""
166 self._run_exec_lines()
167 self._run_exec_files()
168 self._run_cmd_line_code()
169
170 def _run_exec_lines(self):
171 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
172 if not self.exec_lines:
173 return
174 try:
175 self.log.debug("Running code from IPythonApp.exec_lines...")
176 for line in self.exec_lines:
177 try:
178 self.log.info("Running code in user namespace: %s" %
179 line)
180 self.shell.run_cell(line, store_history=False)
181 except:
182 self.log.warn("Error in executing line in user "
183 "namespace: %s" % line)
184 self.shell.showtraceback()
185 except:
186 self.log.warn("Unknown error in handling IPythonApp.exec_lines:")
187 self.shell.showtraceback()
188
189 def _exec_file(self, fname):
190 full_filename = filefind(fname, [u'.', self.ipython_dir])
191 if os.path.isfile(full_filename):
192 if full_filename.endswith(u'.py'):
193 self.log.info("Running file in user namespace: %s" %
194 full_filename)
195 # Ensure that __file__ is always defined to match Python behavior
196 self.shell.user_ns['__file__'] = fname
197 try:
198 self.shell.safe_execfile(full_filename, self.shell.user_ns)
199 finally:
200 del self.shell.user_ns['__file__']
201 elif full_filename.endswith('.ipy'):
202 self.log.info("Running file in user namespace: %s" %
203 full_filename)
204 self.shell.safe_execfile_ipy(full_filename)
205 else:
206 self.log.warn("File does not have a .py or .ipy extension: <%s>"
207 % full_filename)
208
209 def _run_exec_files(self):
210 """Run files from IPythonApp.exec_files"""
211 if not self.exec_files:
212 return
213
214 self.log.debug("Running files in IPythonApp.exec_files...")
215 try:
216 for fname in self.exec_files:
217 self._exec_file(fname)
218 except:
219 self.log.warn("Unknown error in handling IPythonApp.exec_files:")
220 self.shell.showtraceback()
221
222 def _run_cmd_line_code(self):
223 """Run code or file specified at the command-line"""
224 if self.code_to_run:
225 line = self.code_to_run
226 try:
227 self.log.info("Running code given at command line (c=): %s" %
228 line)
229 self.shell.run_cell(line, store_history=False)
230 except:
231 self.log.warn("Error in executing line in user namespace: %s" %
232 line)
233 self.shell.showtraceback()
234
235 # Like Python itself, ignore the second if the first of these is present
236 elif self.file_to_run:
237 fname = self.file_to_run
238 try:
239 self._exec_file(fname)
240 except:
241 self.log.warn("Error in executing file in user namespace: %s" %
242 fname)
243 self.shell.showtraceback()
244
@@ -0,0 +1,267 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 The Base Application class for IPython.parallel apps
5
6 Authors:
7
8 * Brian Granger
9 * Min RK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 from __future__ import with_statement
25
26 import os
27 import logging
28 import re
29 import sys
30
31 from subprocess import Popen, PIPE
32
33 from IPython.core import release
34 from IPython.core.crashhandler import CrashHandler
35 from IPython.core.application import (
36 BaseIPythonApplication,
37 base_aliases as base_ip_aliases,
38 base_flags as base_ip_flags
39 )
40 from IPython.utils.path import expand_path
41
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
43
44 #-----------------------------------------------------------------------------
45 # Module errors
46 #-----------------------------------------------------------------------------
47
48 class PIDFileError(Exception):
49 pass
50
51
52 #-----------------------------------------------------------------------------
53 # Crash handler for this application
54 #-----------------------------------------------------------------------------
55
56
57 _message_template = """\
58 Oops, $self.app_name crashed. We do our best to make it stable, but...
59
60 A crash report was automatically generated with the following information:
61 - A verbatim copy of the crash traceback.
62 - Data on your current $self.app_name configuration.
63
64 It was left in the file named:
65 \t'$self.crash_report_fname'
66 If you can email this file to the developers, the information in it will help
67 them in understanding and correcting the problem.
68
69 You can mail it to: $self.contact_name at $self.contact_email
70 with the subject '$self.app_name Crash Report'.
71
72 If you want to do it now, the following command will work (under Unix):
73 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
74
75 To ensure accurate tracking of this issue, please file a report about it at:
76 $self.bug_tracker
77 """
78
79 class ParallelCrashHandler(CrashHandler):
80 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
81
82 message_template = _message_template
83
84 def __init__(self, app):
85 contact_name = release.authors['Min'][0]
86 contact_email = release.authors['Min'][1]
87 bug_tracker = 'http://github.com/ipython/ipython/issues'
88 super(ParallelCrashHandler,self).__init__(
89 app, contact_name, contact_email, bug_tracker
90 )
91
92
93 #-----------------------------------------------------------------------------
94 # Main application
95 #-----------------------------------------------------------------------------
96 base_aliases = {}
97 base_aliases.update(base_ip_aliases)
98 base_aliases.update({
99 'profile_dir' : 'ProfileDir.location',
100 'log_level' : 'BaseParallelApplication.log_level',
101 'work_dir' : 'BaseParallelApplication.work_dir',
102 'log_to_file' : 'BaseParallelApplication.log_to_file',
103 'clean_logs' : 'BaseParallelApplication.clean_logs',
104 'log_url' : 'BaseParallelApplication.log_url',
105 })
106
107 base_flags = {
108 'log-to-file' : (
109 {'BaseParallelApplication' : {'log_to_file' : True}},
110 "send log output to a file"
111 )
112 }
113 base_flags.update(base_ip_flags)
114
115 class BaseParallelApplication(BaseIPythonApplication):
116 """The base Application for IPython.parallel apps
117
118 Principle extensions to BaseIPyythonApplication:
119
120 * work_dir
121 * remote logging via pyzmq
122 * IOLoop instance
123 """
124
125 crash_handler_class = ParallelCrashHandler
126
127 def _log_level_default(self):
128 # temporarily override default_log_level to INFO
129 return logging.INFO
130
131 work_dir = Unicode(os.getcwdu(), config=True,
132 help='Set the working dir for the process.'
133 )
134 def _work_dir_changed(self, name, old, new):
135 self.work_dir = unicode(expand_path(new))
136
137 log_to_file = Bool(config=True,
138 help="whether to log to a file")
139
140 clean_logs = Bool(False, config=True,
141 help="whether to cleanup old logfiles before starting")
142
143 log_url = Unicode('', config=True,
144 help="The ZMQ URL of the iplogger to aggregate logging.")
145
146 def _config_files_default(self):
147 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
148
149 loop = Instance('zmq.eventloop.ioloop.IOLoop')
150 def _loop_default(self):
151 from zmq.eventloop.ioloop import IOLoop
152 return IOLoop.instance()
153
154 aliases = Dict(base_aliases)
155 flags = Dict(base_flags)
156
157 def initialize(self, argv=None):
158 """initialize the app"""
159 super(BaseParallelApplication, self).initialize(argv)
160 self.to_work_dir()
161 self.reinit_logging()
162
163 def to_work_dir(self):
164 wd = self.work_dir
165 if unicode(wd) != os.getcwdu():
166 os.chdir(wd)
167 self.log.info("Changing to working dir: %s" % wd)
168 # This is the working dir by now.
169 sys.path.insert(0, '')
170
171 def reinit_logging(self):
172 # Remove old log files
173 log_dir = self.profile_dir.log_dir
174 if self.clean_logs:
175 for f in os.listdir(log_dir):
176 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
177 os.remove(os.path.join(log_dir, f))
178 if self.log_to_file:
179 # Start logging to the new log file
180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
181 logfile = os.path.join(log_dir, log_filename)
182 open_log_file = open(logfile, 'w')
183 else:
184 open_log_file = None
185 if open_log_file is not None:
186 self.log.removeHandler(self._log_handler)
187 self._log_handler = logging.StreamHandler(open_log_file)
188 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
189 self._log_handler.setFormatter(self._log_formatter)
190 self.log.addHandler(self._log_handler)
191
192 def write_pid_file(self, overwrite=False):
193 """Create a .pid file in the pid_dir with my pid.
194
195 This must be called after pre_construct, which sets `self.pid_dir`.
196 This raises :exc:`PIDFileError` if the pid file exists already.
197 """
198 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
199 if os.path.isfile(pid_file):
200 pid = self.get_pid_from_file()
201 if not overwrite:
202 raise PIDFileError(
203 'The pid file [%s] already exists. \nThis could mean that this '
204 'server is already running with [pid=%s].' % (pid_file, pid)
205 )
206 with open(pid_file, 'w') as f:
207 self.log.info("Creating pid file: %s" % pid_file)
208 f.write(repr(os.getpid())+'\n')
209
210 def remove_pid_file(self):
211 """Remove the pid file.
212
213 This should be called at shutdown by registering a callback with
214 :func:`reactor.addSystemEventTrigger`. This needs to return
215 ``None``.
216 """
217 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
218 if os.path.isfile(pid_file):
219 try:
220 self.log.info("Removing pid file: %s" % pid_file)
221 os.remove(pid_file)
222 except:
223 self.log.warn("Error removing the pid file: %s" % pid_file)
224
225 def get_pid_from_file(self):
226 """Get the pid from the pid file.
227
228 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
229 """
230 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
231 if os.path.isfile(pid_file):
232 with open(pid_file, 'r') as f:
233 s = f.read().strip()
234 try:
235 pid = int(s)
236 except:
237 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
238 return pid
239 else:
240 raise PIDFileError('pid file not found: %s' % pid_file)
241
242 def check_pid(self, pid):
243 if os.name == 'nt':
244 try:
245 import ctypes
246 # returns 0 if no such process (of ours) exists
247 # positive int otherwise
248 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
249 except Exception:
250 self.log.warn(
251 "Could not determine whether pid %i is running via `OpenProcess`. "
252 " Making the likely assumption that it is."%pid
253 )
254 return True
255 return bool(p)
256 else:
257 try:
258 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
259 output,_ = p.communicate()
260 except OSError:
261 self.log.warn(
262 "Could not determine whether pid %i is running via `ps x`. "
263 " Making the likely assumption that it is."%pid
264 )
265 return True
266 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
267 return pid in pids
@@ -0,0 +1,26 b''
1 """daemonize function from twisted.scripts._twistd_unix."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (c) Twisted Matrix Laboratories.
5 # See Twisted's LICENSE for details.
6 # http://twistedmatrix.com/
7 #-----------------------------------------------------------------------------
8
9 import os, errno
10
11 def daemonize():
12 # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
13 if os.fork(): # launch child and...
14 os._exit(0) # kill off parent
15 os.setsid()
16 if os.fork(): # launch child and...
17 os._exit(0) # kill off parent again.
18 null = os.open('/dev/null', os.O_RDWR)
19 for i in range(3):
20 try:
21 os.dup2(null, i)
22 except OSError, e:
23 if e.errno != errno.EBADF:
24 raise
25 os.close(null)
26
@@ -0,0 +1,215 b''
1 #!/usr/bin/env python
2 """An Application for launching a kernel
3
4 Authors
5 -------
6 * MinRK
7 """
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING.txt, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 # Standard library imports.
20 import os
21 import sys
22
23 # System library imports.
24 import zmq
25
26 # IPython imports.
27 from IPython.core.ultratb import FormattedTB
28 from IPython.core.application import (
29 BaseIPythonApplication, base_flags, base_aliases
30 )
31 from IPython.utils import io
32 from IPython.utils.localinterfaces import LOCALHOST
33 from IPython.utils.traitlets import Any, Instance, Dict, Unicode, Int, Bool
34 from IPython.utils.importstring import import_item
35 # local imports
36 from IPython.zmq.heartbeat import Heartbeat
37 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
38 from IPython.zmq.session import Session
39
40
41 #-----------------------------------------------------------------------------
42 # Flags and Aliases
43 #-----------------------------------------------------------------------------
44
45 kernel_aliases = dict(base_aliases)
46 kernel_aliases.update({
47 'ip' : 'KernelApp.ip',
48 'hb' : 'KernelApp.hb_port',
49 'shell' : 'KernelApp.shell_port',
50 'iopub' : 'KernelApp.iopub_port',
51 'stdin' : 'KernelApp.stdin_port',
52 'parent': 'KernelApp.parent',
53 })
54 if sys.platform.startswith('win'):
55 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
56
57 kernel_flags = dict(base_flags)
58 kernel_flags.update({
59 'no-stdout' : (
60 {'KernelApp' : {'no_stdout' : True}},
61 "redirect stdout to the null device"),
62 'no-stderr' : (
63 {'KernelApp' : {'no_stderr' : True}},
64 "redirect stderr to the null device"),
65 })
66
67
68 #-----------------------------------------------------------------------------
69 # Application class for starting a Kernel
70 #-----------------------------------------------------------------------------
71
72 class KernelApp(BaseIPythonApplication):
73 name='pykernel'
74 aliases = Dict(kernel_aliases)
75 flags = Dict(kernel_flags)
76 classes = [Session]
77 # the kernel class, as an importstring
78 kernel_class = Unicode('IPython.zmq.pykernel.Kernel')
79 kernel = Any()
80 poller = Any() # don't restrict this even though current pollers are all Threads
81 heartbeat = Instance(Heartbeat)
82 session = Instance('IPython.zmq.session.Session')
83 ports = Dict()
84
85 # connection info:
86 ip = Unicode(LOCALHOST, config=True,
87 help="Set the IP or interface on which the kernel will listen.")
88 hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
89 shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
90 iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
91 stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
92
93 # streams, etc.
94 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
95 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
96 outstream_class = Unicode('IPython.zmq.iostream.OutStream', config=True,
97 help="The importstring for the OutStream factory")
98 displayhook_class = Unicode('IPython.zmq.displayhook.DisplayHook', config=True,
99 help="The importstring for the DisplayHook factory")
100
101 # polling
102 parent = Int(0, config=True,
103 help="""kill this process if its parent dies. On Windows, the argument
104 specifies the HANDLE of the parent process, otherwise it is simply boolean.
105 """)
106 interrupt = Int(0, config=True,
107 help="""ONLY USED ON WINDOWS
108 Interrupt this process when the parent is signalled.
109 """)
110
111 def init_crash_handler(self):
112 # Install minimal exception handling
113 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
114 ostream=sys.__stdout__)
115
116 def init_poller(self):
117 if sys.platform == 'win32':
118 if self.interrupt or self.parent:
119 self.poller = ParentPollerWindows(self.interrupt, self.parent)
120 elif self.parent:
121 self.poller = ParentPollerUnix()
122
123 def _bind_socket(self, s, port):
124 iface = 'tcp://%s' % self.ip
125 if port <= 0:
126 port = s.bind_to_random_port(iface)
127 else:
128 s.bind(iface + ':%i'%port)
129 return port
130
131 def init_sockets(self):
132 # Create a context, a session, and the kernel sockets.
133 io.raw_print("Starting the kernel at pid:", os.getpid())
134 context = zmq.Context.instance()
135 # Uncomment this to try closing the context.
136 # atexit.register(context.term)
137
138 self.shell_socket = context.socket(zmq.XREP)
139 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
140 self.log.debug("shell XREP Channel on port: %i"%self.shell_port)
141
142 self.iopub_socket = context.socket(zmq.PUB)
143 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
144 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
145
146 self.stdin_socket = context.socket(zmq.XREQ)
147 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
148 self.log.debug("stdin XREQ Channel on port: %i"%self.stdin_port)
149
150 self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
151 self.hb_port = self.heartbeat.port
152 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
153
154 # Helper to make it easier to connect to an existing kernel, until we have
155 # single-port connection negotiation fully implemented.
156 self.log.info("To connect another client to this kernel, use:")
157 self.log.info("--external shell={0} iopub={1} stdin={2} hb={3}".format(
158 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port))
159
160
161 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
162 stdin=self.stdin_port, hb=self.hb_port)
163
164 def init_session(self):
165 """create our session object"""
166 self.session = Session(config=self.config, username=u'kernel')
167
168 def init_io(self):
169 """redirects stdout/stderr, and installs a display hook"""
170 # Re-direct stdout/stderr, if necessary.
171 if self.no_stdout or self.no_stderr:
172 blackhole = file(os.devnull, 'w')
173 if self.no_stdout:
174 sys.stdout = sys.__stdout__ = blackhole
175 if self.no_stderr:
176 sys.stderr = sys.__stderr__ = blackhole
177
178 # Redirect input streams and set a display hook.
179
180 if self.outstream_class:
181 outstream_factory = import_item(str(self.outstream_class))
182 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
183 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
184 if self.displayhook_class:
185 displayhook_factory = import_item(str(self.displayhook_class))
186 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
187
188 def init_kernel(self):
189 """Create the Kernel object itself"""
190 kernel_factory = import_item(str(self.kernel_class))
191 self.kernel = kernel_factory(config=self.config, session=self.session,
192 shell_socket=self.shell_socket,
193 iopub_socket=self.iopub_socket,
194 stdin_socket=self.stdin_socket,
195 log=self.log
196 )
197 self.kernel.record_ports(self.ports)
198
199 def initialize(self, argv=None):
200 super(KernelApp, self).initialize(argv)
201 self.init_session()
202 self.init_poller()
203 self.init_sockets()
204 self.init_io()
205 self.init_kernel()
206
207 def start(self):
208 self.heartbeat.start()
209 if self.poller is not None:
210 self.poller.start()
211 try:
212 self.kernel.start()
213 except KeyboardInterrupt:
214 pass
215
@@ -0,0 +1,13 b''
1 =======================
2 Log Topic Specification
3 =======================
4
5 we use pyzmq to broadcast log events over a PUB socket. Engines, Controllers, etc. can all
6 broadcast. SUB sockets can be used to view the logs, and ZMQ topics are used to help
7 select out what to follow.
8
9 the PUBHandler object that emits the logs can ascribe topics to log messages. The order is:
10
11 <root_topic>.<loglevel>.<subtopic>[.<etc>]
12
13 root_topic is specified as an attribute
@@ -5,6 +5,7 b' A base class for a configurable application.'
5 5 Authors:
6 6
7 7 * Brian Granger
8 * Min RK
8 9 """
9 10
10 11 #-----------------------------------------------------------------------------
@@ -18,22 +19,29 b' Authors:'
18 19 # Imports
19 20 #-----------------------------------------------------------------------------
20 21
21 from copy import deepcopy
22 22 import logging
23 import os
24 import re
23 25 import sys
26 from copy import deepcopy
24 27
25 28 from IPython.config.configurable import SingletonConfigurable
26 29 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader, Config
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
28 31 )
29 32
30 33 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
34 Unicode, List, Int, Enum, Dict, Instance
32 35 )
33 from IPython.utils.text import indent
36 from IPython.utils.importstring import import_item
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38
39 #-----------------------------------------------------------------------------
40 # function for re-wrapping a helpstring
41 #-----------------------------------------------------------------------------
34 42
35 43 #-----------------------------------------------------------------------------
36 # Descriptions for
44 # Descriptions for the various sections
37 45 #-----------------------------------------------------------------------------
38 46
39 47 flag_description = """
@@ -41,6 +49,8 b" Flags are command-line arguments passed as '--<flag>'."
41 49 These take no parameters, unlike regular key-value arguments.
42 50 They are typically used for setting boolean flags, or enabling
43 51 modes that involve setting multiple options together.
52
53 Flags *always* begin with '--', never just one '-'.
44 54 """.strip() # trim newlines of front and back
45 55
46 56 alias_description = """
@@ -61,12 +71,16 b' This line is evaluated in Python, so simple expressions are allowed, e.g.'
61 71 #-----------------------------------------------------------------------------
62 72
63 73
74 class ApplicationError(Exception):
75 pass
76
77
64 78 class Application(SingletonConfigurable):
65 79 """A singleton application with full configuration support."""
66 80
67 81 # The name of the application, will usually match the name of the command
68 82 # line application
69 app_name = Unicode(u'application')
83 name = Unicode(u'application')
70 84
71 85 # The description of the application that is printed at the beginning
72 86 # of the help.
@@ -85,9 +99,16 b' class Application(SingletonConfigurable):'
85 99 version = Unicode(u'0.0')
86 100
87 101 # The log level for the application
88 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
89 config=True,
90 help="Set the log level (0,10,20,30,40,50).")
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
104 config=True,
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
107 """Adjust the log level when log_level is set."""
108 if isinstance(new, basestring):
109 new = getattr(logging, new)
110 self.log_level = new
111 self.log.setLevel(new)
91 112
92 113 # the alias map for configurables
93 114 aliases = Dict(dict(log_level='Application.log_level'))
@@ -97,6 +118,25 b' class Application(SingletonConfigurable):'
97 118 # this must be a dict of two-tuples, the first element being the Config/dict
98 119 # and the second being the help string for the flag
99 120 flags = Dict()
121 def _flags_changed(self, name, old, new):
122 """ensure flags dict is valid"""
123 for key,value in new.iteritems():
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
127
128
129 # subcommands for launching other applications
130 # if this is not empty, this will be a parent Application
131 # this must be a dict of two-tuples,
132 # the first element being the application class/import string
133 # and the second being the help string for the subcommand
134 subcommands = Dict()
135 # parse_command_line will initialize a subapp, if requested
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
137
138 # extra command-line arguments that don't set config values
139 extra_args = List(Unicode)
100 140
101 141
102 142 def __init__(self, **kwargs):
@@ -105,13 +145,13 b' class Application(SingletonConfigurable):'
105 145 # options.
106 146 self.classes.insert(0, self.__class__)
107 147
108 # ensure self.flags dict is valid
109 for key,value in self.flags.iteritems():
110 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
111 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
112 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
113 148 self.init_logging()
114
149
150 def _config_changed(self, name, old, new):
151 SingletonConfigurable._config_changed(self, name, old, new)
152 self.log.debug('Config changed:')
153 self.log.debug(repr(new))
154
115 155 def init_logging(self):
116 156 """Start logging for this application.
117 157
@@ -125,69 +165,114 b' class Application(SingletonConfigurable):'
125 165 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
126 166 self._log_handler.setFormatter(self._log_formatter)
127 167 self.log.addHandler(self._log_handler)
128
129 def _log_level_changed(self, name, old, new):
130 """Adjust the log level when log_level is set."""
131 self.log.setLevel(new)
168
169 def initialize(self, argv=None):
170 """Do the basic steps to configure me.
171
172 Override in subclasses.
173 """
174 self.parse_command_line(argv)
175
176
177 def start(self):
178 """Start the app mainloop.
179
180 Override in subclasses.
181 """
182 if self.subapp is not None:
183 return self.subapp.start()
132 184
133 185 def print_alias_help(self):
134 """print the alias part of the help"""
186 """Print the alias part of the help."""
135 187 if not self.aliases:
136 188 return
137
138 print "Aliases"
139 print "-------"
140 print self.alias_description
141 print
189
190 lines = ['Aliases']
191 lines.append('-'*len(lines[0]))
192 lines.append('')
193 for p in wrap_paragraphs(self.alias_description):
194 lines.append(p)
195 lines.append('')
142 196
143 197 classdict = {}
144 for c in self.classes:
145 classdict[c.__name__] = c
198 for cls in self.classes:
199 # include all parents (up to, but excluding Configurable) in available names
200 for c in cls.mro()[:-3]:
201 classdict[c.__name__] = c
146 202
147 203 for alias, longname in self.aliases.iteritems():
148 204 classname, traitname = longname.split('.',1)
149 205 cls = classdict[classname]
150 206
151 207 trait = cls.class_traits(config=True)[traitname]
152 help = trait.get_metadata('help')
153 print alias, "(%s)"%longname, ':', trait.__class__.__name__
154 if help:
155 print indent(help)
156 print
208 help = cls.class_get_trait_help(trait)
209 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
210 lines.append(help)
211 lines.append('')
212 print '\n'.join(lines)
157 213
158 214 def print_flag_help(self):
159 """print the flag part of the help"""
215 """Print the flag part of the help."""
160 216 if not self.flags:
161 217 return
162 218
163 print "Flags"
164 print "-----"
165 print self.flag_description
166 print
219 lines = ['Flags']
220 lines.append('-'*len(lines[0]))
221 lines.append('')
222 for p in wrap_paragraphs(self.flag_description):
223 lines.append(p)
224 lines.append('')
167 225
168 226 for m, (cfg,help) in self.flags.iteritems():
169 print '--'+m
170 print indent(help)
171 print
227 lines.append('--'+m)
228 lines.append(indent(dedent(help.strip())))
229 lines.append('')
230 print '\n'.join(lines)
172 231
173 def print_help(self):
174 """Print the help for each Configurable class in self.classes."""
232 def print_subcommands(self):
233 """Print the subcommand part of the help."""
234 if not self.subcommands:
235 return
236
237 lines = ["Subcommands"]
238 lines.append('-'*len(lines[0]))
239 for subc, (cls,help) in self.subcommands.iteritems():
240 lines.append("%s : %s"%(subc, cls))
241 if help:
242 lines.append(indent(dedent(help.strip())))
243 lines.append('')
244 print '\n'.join(lines)
245
246 def print_help(self, classes=False):
247 """Print the help for each Configurable class in self.classes.
248
249 If classes=False (the default), only flags and aliases are printed.
250 """
251 self.print_subcommands()
175 252 self.print_flag_help()
176 253 self.print_alias_help()
177 if self.classes:
178 print "Class parameters"
179 print "----------------"
180 print self.keyvalue_description
181 print
182 254
183 for cls in self.classes:
184 cls.class_print_help()
255 if classes:
256 if self.classes:
257 print "Class parameters"
258 print "----------------"
259 print
260 for p in wrap_paragraphs(self.keyvalue_description):
261 print p
262 print
263
264 for cls in self.classes:
265 cls.class_print_help()
266 print
267 else:
268 print "To see all available configurables, use `--help-all`"
185 269 print
186 270
187 271 def print_description(self):
188 272 """Print the application description."""
189 print self.description
190 print
273 for p in wrap_paragraphs(self.description):
274 print p
275 print
191 276
192 277 def print_version(self):
193 278 """Print the version string."""
@@ -201,30 +286,108 b' class Application(SingletonConfigurable):'
201 286 newconfig._merge(config)
202 287 # Save the combined config as self.config, which triggers the traits
203 288 # events.
204 self.config = config
205
289 self.config = newconfig
290
291 def initialize_subcommand(self, subc, argv=None):
292 """Initialize a subcommand with argv."""
293 subapp,help = self.subcommands.get(subc)
294
295 if isinstance(subapp, basestring):
296 subapp = import_item(subapp)
297
298 # clear existing instances
299 self.__class__.clear_instance()
300 # instantiate
301 self.subapp = subapp.instance()
302 # and initialize subapp
303 self.subapp.initialize(argv)
304
206 305 def parse_command_line(self, argv=None):
207 306 """Parse the command line arguments."""
208 307 argv = sys.argv[1:] if argv is None else argv
209 308
210 if '-h' in argv or '--help' in argv:
309 if self.subcommands and len(argv) > 0:
310 # we have subcommands, and one may have been specified
311 subc, subargv = argv[0], argv[1:]
312 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
313 # it's a subcommand, and *not* a flag or class parameter
314 return self.initialize_subcommand(subc, subargv)
315
316 if '-h' in argv or '--help' in argv or '--help-all' in argv:
211 317 self.print_description()
212 self.print_help()
213 sys.exit(1)
318 self.print_help('--help-all' in argv)
319 self.exit(0)
214 320
215 321 if '--version' in argv:
216 322 self.print_version()
217 sys.exit(1)
323 self.exit(0)
218 324
219 325 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
220 326 flags=self.flags)
221 config = loader.load_config()
327 try:
328 config = loader.load_config()
329 except ArgumentError as e:
330 self.log.fatal(str(e))
331 self.print_description()
332 self.print_help()
333 self.exit(1)
222 334 self.update_config(config)
335 # store unparsed args in extra_args
336 self.extra_args = loader.extra_args
223 337
224 338 def load_config_file(self, filename, path=None):
225 339 """Load a .py based config file by filename and path."""
226 # TODO: this raises IOError if filename does not exist.
227 340 loader = PyFileConfigLoader(filename, path=path)
228 341 config = loader.load_config()
229 342 self.update_config(config)
343
344 def generate_config_file(self):
345 """generate default config file from Configurables"""
346 lines = ["# Configuration file for %s."%self.name]
347 lines.append('')
348 lines.append('c = get_config()')
349 lines.append('')
350 for cls in self.classes:
351 lines.append(cls.class_config_section())
352 return '\n'.join(lines)
353
354 def exit(self, exit_status=0):
355 self.log.debug("Exiting application: %s" % self.name)
356 sys.exit(exit_status)
357
358 #-----------------------------------------------------------------------------
359 # utility functions, for convenience
360 #-----------------------------------------------------------------------------
361
362 def boolean_flag(name, configurable, set_help='', unset_help=''):
363 """Helper for building basic --trait, --no-trait flags.
364
365 Parameters
366 ----------
367
368 name : str
369 The name of the flag.
370 configurable : str
371 The 'Class.trait' string of the trait to be set/unset with the flag
372 set_help : unicode
373 help string for --name flag
374 unset_help : unicode
375 help string for --no-name flag
376
377 Returns
378 -------
379
380 cfg : dict
381 A dict with two keys: 'name', and 'no-name', for setting and unsetting
382 the trait, respectively.
383 """
384 # default helpstrings
385 set_help = set_help or "set %s=True"%configurable
386 unset_help = unset_help or "set %s=False"%configurable
387
388 cls,trait = configurable.split('.')
389
390 setter = {cls : {trait : True}}
391 unsetter = {cls : {trait : False}}
392 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
230 393
@@ -7,10 +7,11 b' Authors:'
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 * Min RK
10 11 """
11 12
12 13 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2010 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
14 15 #
15 16 # Distributed under the terms of the BSD License. The full license is in
16 17 # the file COPYING, distributed as part of this software.
@@ -20,12 +21,12 b' Authors:'
20 21 # Imports
21 22 #-----------------------------------------------------------------------------
22 23
23 from copy import deepcopy
24 24 import datetime
25 from copy import deepcopy
25 26
26 27 from loader import Config
27 28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.text import indent
29 from IPython.utils.text import indent, wrap_paragraphs
29 30
30 31
31 32 #-----------------------------------------------------------------------------
@@ -44,14 +45,13 b' class MultipleInstanceError(ConfigurableError):'
44 45 # Configurable implementation
45 46 #-----------------------------------------------------------------------------
46 47
47
48 48 class Configurable(HasTraits):
49 49
50 50 config = Instance(Config,(),{})
51 51 created = None
52 52
53 53 def __init__(self, **kwargs):
54 """Create a conigurable given a config config.
54 """Create a configurable given a config config.
55 55
56 56 Parameters
57 57 ----------
@@ -146,18 +146,72 b' class Configurable(HasTraits):'
146 146 final_help = []
147 147 final_help.append(u'%s options' % cls.__name__)
148 148 final_help.append(len(final_help[0])*u'-')
149 for k, v in cls_traits.items():
150 help = v.get_metadata('help')
151 header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
152 final_help.append(header)
153 if help is not None:
154 final_help.append(indent(help))
149 for k,v in cls.class_traits(config=True).iteritems():
150 help = cls.class_get_trait_help(v)
151 final_help.append(help)
155 152 return '\n'.join(final_help)
153
154 @classmethod
155 def class_get_trait_help(cls, trait):
156 """Get the help string for a single trait."""
157 lines = []
158 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
159 lines.append(header)
160 try:
161 dvr = repr(trait.get_default_value())
162 except Exception:
163 dvr = None # ignore defaults we can't construct
164 if dvr is not None:
165 if len(dvr) > 64:
166 dvr = dvr[:61]+'...'
167 lines.append(indent('Default: %s'%dvr, 4))
168 if 'Enum' in trait.__class__.__name__:
169 # include Enum choices
170 lines.append(indent('Choices: %r'%(trait.values,)))
171
172 help = trait.get_metadata('help')
173 if help is not None:
174 help = '\n'.join(wrap_paragraphs(help, 76))
175 lines.append(indent(help, 4))
176 return '\n'.join(lines)
156 177
157 178 @classmethod
158 179 def class_print_help(cls):
180 """Get the help string for a single trait and print it."""
159 181 print cls.class_get_help()
160 182
183 @classmethod
184 def class_config_section(cls):
185 """Get the config class config section"""
186 def c(s):
187 """return a commented, wrapped block."""
188 s = '\n\n'.join(wrap_paragraphs(s, 78))
189
190 return '# ' + s.replace('\n', '\n# ')
191
192 # section header
193 breaker = '#' + '-'*78
194 s = "# %s configuration"%cls.__name__
195 lines = [breaker, s, breaker, '']
196 # get the description trait
197 desc = cls.class_traits().get('description')
198 if desc:
199 desc = desc.default_value
200 else:
201 # no description trait, use __doc__
202 desc = getattr(cls, '__doc__', '')
203 if desc:
204 lines.append(c(desc))
205 lines.append('')
206
207 for name,trait in cls.class_traits(config=True).iteritems():
208 help = trait.get_metadata('help') or ''
209 lines.append(c(help))
210 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
211 lines.append('')
212 return '\n'.join(lines)
213
214
161 215
162 216 class SingletonConfigurable(Configurable):
163 217 """A configurable that only allows one instance.
@@ -168,7 +222,32 b' class SingletonConfigurable(Configurable):'
168 222 """
169 223
170 224 _instance = None
171
225
226 @classmethod
227 def _walk_mro(cls):
228 """Walk the cls.mro() for parent classes that are also singletons
229
230 For use in instance()
231 """
232
233 for subclass in cls.mro():
234 if issubclass(cls, subclass) and \
235 issubclass(subclass, SingletonConfigurable) and \
236 subclass != SingletonConfigurable:
237 yield subclass
238
239 @classmethod
240 def clear_instance(cls):
241 """unset _instance for this class and singleton parents.
242 """
243 if not cls.initialized():
244 return
245 for subclass in cls._walk_mro():
246 if isinstance(subclass._instance, cls):
247 # only clear instances that are instances
248 # of the calling class
249 subclass._instance = None
250
172 251 @classmethod
173 252 def instance(cls, *args, **kwargs):
174 253 """Returns a global instance of this class.
@@ -202,14 +281,10 b' class SingletonConfigurable(Configurable):'
202 281 if cls._instance is None:
203 282 inst = cls(*args, **kwargs)
204 283 # Now make sure that the instance will also be returned by
205 # the subclasses instance attribute.
206 for subclass in cls.mro():
207 if issubclass(cls, subclass) and \
208 issubclass(subclass, SingletonConfigurable) and \
209 subclass != SingletonConfigurable:
210 subclass._instance = inst
211 else:
212 break
284 # parent classes' _instance attribute.
285 for subclass in cls._walk_mro():
286 subclass._instance = inst
287
213 288 if isinstance(cls._instance, cls):
214 289 return cls._instance
215 290 else:
@@ -223,3 +298,18 b' class SingletonConfigurable(Configurable):'
223 298 """Has an instance been created?"""
224 299 return hasattr(cls, "_instance") and cls._instance is not None
225 300
301
302 class LoggingConfigurable(Configurable):
303 """A parent class for Configurables that log.
304
305 Subclasses have a log trait, and the default behavior
306 is to get the logger from the currently running Application
307 via Application.instance().log.
308 """
309
310 log = Instance('logging.Logger')
311 def _log_default(self):
312 from IPython.config.application import Application
313 return Application.instance().log
314
315
@@ -4,10 +4,11 b' Authors'
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 * Min RK
7 8 """
8 9
9 10 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2009 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
11 12 #
12 13 # Distributed under the terms of the BSD License. The full license is in
13 14 # the file COPYING, distributed as part of this software.
@@ -22,7 +23,7 b' import re'
22 23 import sys
23 24
24 25 from IPython.external import argparse
25 from IPython.utils.path import filefind
26 from IPython.utils.path import filefind, get_ipython_dir
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Exceptions
@@ -36,6 +37,9 b' class ConfigError(Exception):'
36 37 class ConfigLoaderError(ConfigError):
37 38 pass
38 39
40 class ArgumentError(ConfigLoaderError):
41 pass
42
39 43 #-----------------------------------------------------------------------------
40 44 # Argparse fix
41 45 #-----------------------------------------------------------------------------
@@ -265,23 +269,40 b' class PyFileConfigLoader(FileConfigLoader):'
265 269 def _read_file_as_dict(self):
266 270 """Load the config file into self.config, with recursive loading."""
267 271 # This closure is made available in the namespace that is used
268 # to exec the config file. This allows users to call
272 # to exec the config file. It allows users to call
269 273 # load_subconfig('myconfig.py') to load config files recursively.
270 274 # It needs to be a closure because it has references to self.path
271 275 # and self.config. The sub-config is loaded with the same path
272 276 # as the parent, but it uses an empty config which is then merged
273 277 # with the parents.
274 def load_subconfig(fname):
275 loader = PyFileConfigLoader(fname, self.path)
278
279 # If a profile is specified, the config file will be loaded
280 # from that profile
281
282 def load_subconfig(fname, profile=None):
283 # import here to prevent circular imports
284 from IPython.core.profiledir import ProfileDir, ProfileDirError
285 if profile is not None:
286 try:
287 profile_dir = ProfileDir.find_profile_dir_by_name(
288 get_ipython_dir(),
289 profile,
290 )
291 except ProfileDirError:
292 return
293 path = profile_dir.location
294 else:
295 path = self.path
296 loader = PyFileConfigLoader(fname, path)
276 297 try:
277 298 sub_config = loader.load_config()
278 299 except IOError:
279 300 # Pass silently if the sub config is not there. This happens
280 # when a user us using a profile, but not the default config.
301 # when a user s using a profile, but not the default config.
281 302 pass
282 303 else:
283 304 self.config._merge(sub_config)
284
305
285 306 # Again, this needs to be a closure and should be used in config
286 307 # files to get the config being loaded.
287 308 def get_config():
@@ -304,7 +325,7 b' class CommandLineConfigLoader(ConfigLoader):'
304 325 here.
305 326 """
306 327
307 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.+')
328 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.*')
308 329 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
309 330
310 331 class KeyValueConfigLoader(CommandLineConfigLoader):
@@ -346,15 +367,27 b' class KeyValueConfigLoader(CommandLineConfigLoader):'
346 367 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
347 368 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
348 369 """
370 self.clear()
349 371 if argv is None:
350 372 argv = sys.argv[1:]
351 373 self.argv = argv
352 374 self.aliases = aliases or {}
353 375 self.flags = flags or {}
376
377
378 def clear(self):
379 super(KeyValueConfigLoader, self).clear()
380 self.extra_args = []
381
354 382
355 383 def load_config(self, argv=None, aliases=None, flags=None):
356 384 """Parse the configuration and generate the Config object.
357
385
386 After loading, any arguments that are not key-value or
387 flags will be stored in self.extra_args - a list of
388 unparsed command-line arguments. This is used for
389 arguments such as input files or subcommands.
390
358 391 Parameters
359 392 ----------
360 393 argv : list, optional
@@ -379,7 +412,7 b' class KeyValueConfigLoader(CommandLineConfigLoader):'
379 412 aliases = self.aliases
380 413 if flags is None:
381 414 flags = self.flags
382
415
383 416 for item in argv:
384 417 if kv_pattern.match(item):
385 418 lhs,rhs = item.split('=',1)
@@ -403,14 +436,21 b' class KeyValueConfigLoader(CommandLineConfigLoader):'
403 436 m = item[2:]
404 437 cfg,_ = flags.get(m, (None,None))
405 438 if cfg is None:
406 raise ValueError("Unrecognized flag: %r"%item)
439 raise ArgumentError("Unrecognized flag: %r"%item)
407 440 elif isinstance(cfg, (dict, Config)):
408 # update self.config with Config:
409 self.config.update(cfg)
441 # don't clobber whole config sections, update
442 # each section from config:
443 for sec,c in cfg.iteritems():
444 self.config[sec].update(c)
410 445 else:
411 446 raise ValueError("Invalid flag: %r"%flag)
447 elif item.startswith('-'):
448 # this shouldn't ever be valid
449 raise ArgumentError("Invalid argument: %r"%item)
412 450 else:
413 raise ValueError("Invalid argument: %r"%item)
451 # keep all args that aren't valid in a list,
452 # in case our parent knows what to do with them.
453 self.extra_args.append(item)
414 454 return self.config
415 455
416 456 class ArgParseConfigLoader(CommandLineConfigLoader):
@@ -1,24 +1,25 b''
1 1 c = get_config()
2 app = c.InteractiveShellApp
2 3
3 4 # This can be used at any point in a config file to load a sub config
4 5 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6 load_subconfig('ipython_config.py', profile='default')
6 7
7 8 lines = """
8 from IPython.kernel.client import *
9 from IPython.parallel import *
9 10 """
10 11
11 12 # You have to make sure that attributes that are containers already
12 13 # exist before using them. Simple assigning a new list will override
13 14 # all previous values.
14 if hasattr(c.Global, 'exec_lines'):
15 c.Global.exec_lines.append(lines)
15 if hasattr(app, 'exec_lines'):
16 app.exec_lines.append(lines)
16 17 else:
17 c.Global.exec_lines = [lines]
18 app.exec_lines = [lines]
18 19
19 20 # Load the parallelmagic extension to enable %result, %px, %autopx magics.
20 if hasattr(c.Global, 'extensions'):
21 c.Global.extensions.append('parallelmagic')
21 if hasattr(app, 'extensions'):
22 app.extensions.append('parallelmagic')
22 23 else:
23 c.Global.extensions = ['parallelmagic']
24 app.extensions = ['parallelmagic']
24 25
@@ -1,8 +1,9 b''
1 1 c = get_config()
2 app = c.InteractiveShellApp
2 3
3 4 # This can be used at any point in a config file to load a sub config
4 5 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6 load_subconfig('ipython_config.py', profile='default')
6 7
7 8 lines = """
8 9 import cmath
@@ -12,8 +13,9 b' from math import *'
12 13 # You have to make sure that attributes that are containers already
13 14 # exist before using them. Simple assigning a new list will override
14 15 # all previous values.
15 if hasattr(c.Global, 'exec_lines'):
16 c.Global.exec_lines.append(lines)
16
17 if hasattr(app, 'exec_lines'):
18 app.exec_lines.append(lines)
17 19 else:
18 c.Global.exec_lines = [lines]
20 app.exec_lines = [lines]
19 21
@@ -1,13 +1,14 b''
1 1 c = get_config()
2 app = c.InteractiveShellApp
2 3
3 4 # This can be used at any point in a config file to load a sub config
4 5 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6 load_subconfig('ipython_config.py', profile='default')
6 7
7 8 lines = """
8 9 import matplotlib
9 %gui -a wx
10 matplotlib.use('wxagg')
10 %gui qt
11 matplotlib.use('qtagg')
11 12 matplotlib.interactive(True)
12 13 from matplotlib import pyplot as plt
13 14 from matplotlib.pyplot import *
@@ -16,7 +17,7 b' from matplotlib.pyplot import *'
16 17 # You have to make sure that attributes that are containers already
17 18 # exist before using them. Simple assigning a new list will override
18 19 # all previous values.
19 if hasattr(c.Global, 'exec_lines'):
20 c.Global.exec_lines.append(lines)
20 if hasattr(app, 'exec_lines'):
21 app.exec_lines.append(lines)
21 22 else:
22 c.Global.exec_lines = [lines] No newline at end of file
23 app.exec_lines = [lines] No newline at end of file
@@ -1,8 +1,9 b''
1 1 c = get_config()
2 app = c.InteractiveShellApp
2 3
3 4 # This can be used at any point in a config file to load a sub config
4 5 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6 load_subconfig('ipython_config.py', profile='default')
6 7
7 8 c.InteractiveShell.prompt_in1 = '\C_LightGreen\u@\h\C_LightBlue[\C_LightCyan\Y1\C_LightBlue]\C_Green|\#> '
8 9 c.InteractiveShell.prompt_in2 = '\C_Green|\C_LightGreen\D\C_Green> '
@@ -23,7 +24,7 b' lines = """'
23 24 # You have to make sure that attributes that are containers already
24 25 # exist before using them. Simple assigning a new list will override
25 26 # all previous values.
26 if hasattr(c.Global, 'exec_lines'):
27 c.Global.exec_lines.append(lines)
27 if hasattr(app, 'exec_lines'):
28 app.exec_lines.append(lines)
28 29 else:
29 c.Global.exec_lines = [lines] No newline at end of file
30 app.exec_lines = [lines] No newline at end of file
@@ -1,8 +1,9 b''
1 1 c = get_config()
2 app = c.InteractiveShellApp
2 3
3 4 # This can be used at any point in a config file to load a sub config
4 5 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6 load_subconfig('ipython_config.py', profile='default')
6 7
7 8 lines = """
8 9 from __future__ import division
@@ -16,14 +17,14 b" f, g, h = map(Function, 'fgh')"
16 17 # exist before using them. Simple assigning a new list will override
17 18 # all previous values.
18 19
19 if hasattr(c.Global, 'exec_lines'):
20 c.Global.exec_lines.append(lines)
20 if hasattr(app, 'exec_lines'):
21 app.exec_lines.append(lines)
21 22 else:
22 c.Global.exec_lines = [lines]
23 app.exec_lines = [lines]
23 24
24 25 # Load the sympy_printing extension to enable nice printing of sympy expr's.
25 if hasattr(c.Global, 'extensions'):
26 c.Global.extensions.append('sympy_printing')
26 if hasattr(app, 'extensions'):
27 app.extensions.append('sympyprinting')
27 28 else:
28 c.Global.extensions = ['sympy_printing']
29 app.extensions = ['sympyprinting']
29 30
@@ -42,12 +42,13 b' class Foo(Configurable):'
42 42
43 43 class Bar(Configurable):
44 44
45 b = Int(0, config=True, help="The integer b.")
45 46 enabled = Bool(True, config=True, help="Enable bar.")
46 47
47 48
48 49 class MyApp(Application):
49 50
50 app_name = Unicode(u'myapp')
51 name = Unicode(u'myapp')
51 52 running = Bool(False, config=True,
52 53 help="Is the app running?")
53 54 classes = List([Bar, Foo])
@@ -71,30 +72,30 b' class TestApplication(TestCase):'
71 72
72 73 def test_basic(self):
73 74 app = MyApp()
74 self.assertEquals(app.app_name, u'myapp')
75 self.assertEquals(app.name, u'myapp')
75 76 self.assertEquals(app.running, False)
76 77 self.assertEquals(app.classes, [MyApp,Bar,Foo])
77 78 self.assertEquals(app.config_file, u'')
78 79
79 80 def test_config(self):
80 81 app = MyApp()
81 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
82 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=50"])
82 83 config = app.config
83 84 self.assertEquals(config.Foo.i, 10)
84 85 self.assertEquals(config.Foo.j, 10)
85 86 self.assertEquals(config.Bar.enabled, False)
86 self.assertEquals(config.MyApp.log_level,0)
87 self.assertEquals(config.MyApp.log_level,50)
87 88
88 89 def test_config_propagation(self):
89 90 app = MyApp()
90 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=0"])
91 app.parse_command_line(["i=10","Foo.j=10","enabled=False","log_level=50"])
91 92 app.init_foo()
92 93 app.init_bar()
93 94 self.assertEquals(app.foo.i, 10)
94 95 self.assertEquals(app.foo.j, 10)
95 96 self.assertEquals(app.bar.enabled, False)
96 97
97 def test_alias(self):
98 def test_flags(self):
98 99 app = MyApp()
99 100 app.parse_command_line(["--disable"])
100 101 app.init_bar()
@@ -103,3 +104,32 b' class TestApplication(TestCase):'
103 104 app.init_bar()
104 105 self.assertEquals(app.bar.enabled, True)
105 106
107 def test_aliases(self):
108 app = MyApp()
109 app.parse_command_line(["i=5", "j=10"])
110 app.init_foo()
111 self.assertEquals(app.foo.i, 5)
112 app.init_foo()
113 self.assertEquals(app.foo.j, 10)
114
115 def test_flag_clobber(self):
116 """test that setting flags doesn't clobber existing settings"""
117 app = MyApp()
118 app.parse_command_line(["Bar.b=5", "--disable"])
119 app.init_bar()
120 self.assertEquals(app.bar.enabled, False)
121 self.assertEquals(app.bar.b, 5)
122 app.parse_command_line(["--enable", "Bar.b=10"])
123 app.init_bar()
124 self.assertEquals(app.bar.enabled, True)
125 self.assertEquals(app.bar.b, 10)
126
127 def test_extra_args(self):
128 app = MyApp()
129 app.parse_command_line(['extra', "Bar.b=5", "--disable", 'args'])
130 app.init_bar()
131 self.assertEquals(app.bar.enabled, False)
132 self.assertEquals(app.bar.b, 5)
133 self.assertEquals(app.extra_args, ['extra', 'args'])
134
135
@@ -48,8 +48,10 b' class MyConfigurable(Configurable):'
48 48 mc_help=u"""MyConfigurable options
49 49 ----------------------
50 50 MyConfigurable.a : Int
51 Default: 1
51 52 The integer a.
52 53 MyConfigurable.b : Float
54 Default: 1.0
53 55 The integer b."""
54 56
55 57 class Foo(Configurable):
@@ -123,6 +123,13 b' class TestKeyValueCL(TestCase):'
123 123 self.assertEquals(config.Foo.Bar.value, 10)
124 124 self.assertEquals(config.Foo.Bam.value, range(10))
125 125 self.assertEquals(config.D.C.value, 'hi there')
126
127 def test_extra_args(self):
128 cl = KeyValueConfigLoader()
129 config = cl.load_config(['a=5', 'b', 'c=10', 'd'])
130 self.assertEquals(cl.extra_args, ['b', 'd'])
131 self.assertEquals(config.a, 5)
132 self.assertEquals(config.c, 10)
126 133
127 134
128 135 class TestConfig(TestCase):
This diff has been collapsed as it changes many lines, (616 lines changed) Show them Hide them
@@ -12,13 +12,12 b' Authors:'
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 * Min RK
15 16
16 Notes
17 -----
18 17 """
19 18
20 19 #-----------------------------------------------------------------------------
21 # Copyright (C) 2008-2009 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
22 21 #
23 22 # Distributed under the terms of the BSD License. The full license is in
24 23 # the file COPYING, distributed as part of this software.
@@ -30,439 +29,262 b' Notes'
30 29
31 30 import logging
32 31 import os
32 import shutil
33 33 import sys
34 34
35 from IPython.config.application import Application
36 from IPython.config.configurable import Configurable
37 from IPython.config.loader import Config
35 38 from IPython.core import release, crashhandler
39 from IPython.core.profiledir import ProfileDir, ProfileDirError
36 40 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 from IPython.config.loader import (
38 PyFileConfigLoader,
39 ArgParseConfigLoader,
40 Config,
41 )
41 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Classes and functions
45 45 #-----------------------------------------------------------------------------
46 46
47 class ApplicationError(Exception):
48 pass
49
50
51 class BaseAppConfigLoader(ArgParseConfigLoader):
52 """Default command line options for IPython based applications."""
53
54 def _add_ipython_dir(self, parser):
55 """Add the --ipython-dir option to the parser."""
56 paa = parser.add_argument
57 paa('--ipython-dir',
58 dest='Global.ipython_dir',type=unicode,
59 help=
60 """Set to override default location of the IPython directory
61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
62 specified through the environment variable IPYTHON_DIR.""",
63 metavar='Global.ipython_dir')
64
65 def _add_log_level(self, parser):
66 """Add the --log-level option to the parser."""
67 paa = parser.add_argument
68 paa('--log-level',
69 dest="Global.log_level",type=int,
70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
71 metavar='Global.log_level')
72
73 def _add_version(self, parser):
74 """Add the --version option to the parser."""
75 parser.add_argument('--version', action="version",
76 version=self.version)
77
78 def _add_arguments(self):
79 self._add_ipython_dir(self.parser)
80 self._add_log_level(self.parser)
81 try: # Old versions of argparse don't have a version action
82 self._add_version(self.parser)
83 except Exception:
84 pass
85
86
87 class Application(object):
88 """Load a config, construct configurables and set them running.
89
90 The configuration of an application can be done via three different Config
91 objects, which are loaded and ultimately merged into a single one used
92 from that point on by the app. These are:
93
94 1. default_config: internal defaults, implemented in code.
95 2. file_config: read from the filesystem.
96 3. command_line_config: read from the system's command line flags.
97
98 During initialization, 3 is actually read before 2, since at the
99 command-line one may override the location of the file to be read. But the
100 above is the order in which the merge is made.
101 """
102
103 name = u'ipython'
104 description = 'IPython: an enhanced interactive Python shell.'
105 #: Usage message printed by argparse. If None, auto-generate
106 usage = None
107 #: The command line config loader. Subclass of ArgParseConfigLoader.
108 command_line_loader = BaseAppConfigLoader
109 #: The name of the config file to load, determined at runtime
110 config_file_name = None
111 #: The name of the default config file. Track separately from the actual
112 #: name because some logic happens only if we aren't using the default.
113 default_config_file_name = u'ipython_config.py'
114 default_log_level = logging.WARN
115 #: Set by --profile option
116 profile_name = None
117 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
118 ipython_dir = None
119 #: Internal defaults, implemented in code.
120 default_config = None
121 #: Read from the filesystem.
122 file_config = None
123 #: Read from the system's command line flags.
124 command_line_config = None
125 #: The final config that will be passed to the main object.
126 master_config = None
127 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
128 argv = None
129 #: extra arguments computed by the command-line loader
130 extra_args = None
131 #: The class to use as the crash handler.
132 crash_handler_class = crashhandler.CrashHandler
133
134 # Private attributes
135 _exiting = False
136 _initialized = False
137
138 def __init__(self, argv=None):
139 self.argv = sys.argv[1:] if argv is None else argv
140 self.init_logger()
141
142 def init_logger(self):
143 self.log = logging.getLogger(self.__class__.__name__)
144 # This is used as the default until the command line arguments are read.
145 self.log.setLevel(self.default_log_level)
146 self._log_handler = logging.StreamHandler()
147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
148 self._log_handler.setFormatter(self._log_formatter)
149 self.log.addHandler(self._log_handler)
150
151 def _set_log_level(self, level):
152 self.log.setLevel(level)
153
154 def _get_log_level(self):
155 return self.log.level
156
157 log_level = property(_get_log_level, _set_log_level)
158
159 def initialize(self):
160 """Initialize the application.
161
162 Loads all configuration information and sets all application state, but
163 does not start any relevant processing (typically some kind of event
164 loop).
165
166 Once this method has been called, the application is flagged as
167 initialized and the method becomes a no-op."""
168
169 if self._initialized:
170 return
171
172 # The first part is protected with an 'attempt' wrapper, that will log
173 # failures with the basic system traceback machinery. Once our crash
174 # handler is in place, we can let any subsequent exception propagate,
175 # as our handler will log it with much better detail than the default.
176 self.attempt(self.create_crash_handler)
177
178 # Configuration phase
179 # Default config (internally hardwired in application code)
180 self.create_default_config()
181 self.log_default_config()
182 self.set_default_config_log_level()
183
184 # Command-line config
185 self.pre_load_command_line_config()
186 self.load_command_line_config()
187 self.set_command_line_config_log_level()
188 self.post_load_command_line_config()
189 self.log_command_line_config()
190 47
191 # Find resources needed for filesystem access, using information from
192 # the above two
193 self.find_ipython_dir()
194 self.find_resources()
195 self.find_config_file_name()
196 self.find_config_file_paths()
197
198 # File-based config
199 self.pre_load_file_config()
200 self.load_file_config()
201 self.set_file_config_log_level()
202 self.post_load_file_config()
203 self.log_file_config()
48 #-----------------------------------------------------------------------------
49 # Base Application Class
50 #-----------------------------------------------------------------------------
204 51
205 # Merge all config objects into a single one the app can then use
206 self.merge_configs()
207 self.log_master_config()
52 # aliases and flags
208 53
209 # Construction phase
210 self.pre_construct()
211 self.construct()
212 self.post_construct()
54 base_aliases = dict(
55 profile='BaseIPythonApplication.profile',
56 ipython_dir='BaseIPythonApplication.ipython_dir',
57 )
213 58
214 # Done, flag as such and
215 self._initialized = True
59 base_flags = dict(
60 debug = ({'Application' : {'log_level' : logging.DEBUG}},
61 "set log level to logging.DEBUG (maximize logging output)"),
62 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
63 "set log level to logging.CRITICAL (minimize logging output)"),
64 init = ({'BaseIPythonApplication' : {
65 'copy_config_files' : True,
66 'auto_create' : True}
67 }, "Initialize profile with default config files")
68 )
216 69
217 def start(self):
218 """Start the application."""
219 self.initialize()
220 self.start_app()
221 70
71 class BaseIPythonApplication(Application):
72
73 name = Unicode(u'ipython')
74 description = Unicode(u'IPython: an enhanced interactive Python shell.')
75 version = Unicode(release.version)
76
77 aliases = Dict(base_aliases)
78 flags = Dict(base_flags)
79
80 # Track whether the config_file has changed,
81 # because some logic happens only if we aren't using the default.
82 config_file_specified = Bool(False)
83
84 config_file_name = Unicode(u'ipython_config.py')
85 def _config_file_name_default(self):
86 return self.name.replace('-','_') + u'_config.py'
87 def _config_file_name_changed(self, name, old, new):
88 if new != old:
89 self.config_file_specified = True
90
91 # The directory that contains IPython's builtin profiles.
92 builtin_profile_dir = Unicode(
93 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
94 )
95
96 config_file_paths = List(Unicode)
97 def _config_file_paths_default(self):
98 return [os.getcwdu()]
99
100 profile = Unicode(u'default', config=True,
101 help="""The IPython profile to use."""
102 )
103 def _profile_changed(self, name, old, new):
104 self.builtin_profile_dir = os.path.join(
105 get_ipython_package_dir(), u'config', u'profile', new
106 )
107
108 ipython_dir = Unicode(get_ipython_dir(), config=True,
109 help="""
110 The name of the IPython directory. This directory is used for logging
111 configuration (through profiles), history storage, etc. The default
112 is usually $HOME/.ipython. This options can also be specified through
113 the environment variable IPYTHON_DIR.
114 """
115 )
116
117 overwrite = Bool(False, config=True,
118 help="""Whether to overwrite existing config files when copying""")
119 auto_create = Bool(False, config=True,
120 help="""Whether to create profile dir if it doesn't exist""")
121
122 config_files = List(Unicode)
123 def _config_files_default(self):
124 return [u'ipython_config.py']
125
126 copy_config_files = Bool(False, config=True,
127 help="""Whether to install the default config files into the profile dir.
128 If a new profile is being created, and IPython contains config files for that
129 profile, then they will be staged into the new directory. Otherwise,
130 default config files will be automatically generated.
131 """)
132
133 # The class to use as the crash handler.
134 crash_handler_class = Type(crashhandler.CrashHandler)
135
136 def __init__(self, **kwargs):
137 super(BaseIPythonApplication, self).__init__(**kwargs)
138 # ensure even default IPYTHON_DIR exists
139 if not os.path.exists(self.ipython_dir):
140 self._ipython_dir_changed('ipython_dir', self.ipython_dir, self.ipython_dir)
141
222 142 #-------------------------------------------------------------------------
223 143 # Various stages of Application creation
224 144 #-------------------------------------------------------------------------
225 145
226 def create_crash_handler(self):
146 def init_crash_handler(self):
227 147 """Create a crash handler, typically setting sys.excepthook to it."""
228 148 self.crash_handler = self.crash_handler_class(self)
229 149 sys.excepthook = self.crash_handler
230 150
231 def create_default_config(self):
232 """Create defaults that can't be set elsewhere.
233
234 For the most part, we try to set default in the class attributes
235 of Configurables. But, defaults the top-level Application (which is
236 not a HasTraits or Configurables) are not set in this way. Instead
237 we set them here. The Global section is for variables like this that
238 don't belong to a particular configurable.
239 """
240 c = Config()
241 c.Global.ipython_dir = get_ipython_dir()
242 c.Global.log_level = self.log_level
243 self.default_config = c
244
245 def log_default_config(self):
246 self.log.debug('Default config loaded:')
247 self.log.debug(repr(self.default_config))
248
249 def set_default_config_log_level(self):
250 try:
251 self.log_level = self.default_config.Global.log_level
252 except AttributeError:
253 # Fallback to the default_log_level class attribute
254 pass
255
256 def create_command_line_config(self):
257 """Create and return a command line config loader."""
258 return self.command_line_loader(
259 self.argv,
260 description=self.description,
261 version=release.version,
262 usage=self.usage
263 )
264
265 def pre_load_command_line_config(self):
266 """Do actions just before loading the command line config."""
267 pass
268
269 def load_command_line_config(self):
270 """Load the command line config."""
271 loader = self.create_command_line_config()
272 self.command_line_config = loader.load_config()
273 self.extra_args = loader.get_extra_args()
274
275 def set_command_line_config_log_level(self):
276 try:
277 self.log_level = self.command_line_config.Global.log_level
278 except AttributeError:
279 pass
280
281 def post_load_command_line_config(self):
282 """Do actions just after loading the command line config."""
283 pass
284
285 def log_command_line_config(self):
286 self.log.debug("Command line config loaded:")
287 self.log.debug(repr(self.command_line_config))
288
289 def find_ipython_dir(self):
290 """Set the IPython directory.
291
292 This sets ``self.ipython_dir``, but the actual value that is passed to
293 the application is kept in either ``self.default_config`` or
294 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
295 ``sys.path`` so config files there can be referenced by other config
296 files.
297 """
298
299 try:
300 self.ipython_dir = self.command_line_config.Global.ipython_dir
301 except AttributeError:
302 self.ipython_dir = self.default_config.Global.ipython_dir
303 sys.path.append(os.path.abspath(self.ipython_dir))
304 if not os.path.isdir(self.ipython_dir):
305 os.makedirs(self.ipython_dir, mode=0777)
306 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
307
308 def find_resources(self):
309 """Find other resources that need to be in place.
310
311 Things like cluster directories need to be in place to find the
312 config file. These happen right after the IPython directory has
313 been set.
314 """
315 pass
316
317 def find_config_file_name(self):
318 """Find the config file name for this application.
319
320 This must set ``self.config_file_name`` to the filename of the
321 config file to use (just the filename). The search paths for the
322 config file are set in :meth:`find_config_file_paths` and then passed
323 to the config file loader where they are resolved to an absolute path.
324
325 If a profile has been set at the command line, this will resolve it.
326 """
327 try:
328 self.config_file_name = self.command_line_config.Global.config_file
329 except AttributeError:
330 pass
331 else:
332 return
333
334 try:
335 self.profile_name = self.command_line_config.Global.profile
336 except AttributeError:
337 # Just use the default as there is no profile
338 self.config_file_name = self.default_config_file_name
339 else:
340 # Use the default config file name and profile name if set
341 # to determine the used config file name.
342 name_parts = self.default_config_file_name.split('.')
343 name_parts.insert(1, u'_' + self.profile_name + u'.')
344 self.config_file_name = ''.join(name_parts)
345
346 def find_config_file_paths(self):
347 """Set the search paths for resolving the config file.
348
349 This must set ``self.config_file_paths`` to a sequence of search
350 paths to pass to the config file loader.
351 """
352 # Include our own profiles directory last, so that users can still find
353 # our shipped copies of builtin profiles even if they don't have them
354 # in their local ipython directory.
355 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
356 self.config_file_paths = (os.getcwdu(), self.ipython_dir, prof_dir)
357
358 def pre_load_file_config(self):
359 """Do actions before the config file is loaded."""
360 pass
361
362 def load_file_config(self, suppress_errors=True):
151 def _ipython_dir_changed(self, name, old, new):
152 if old in sys.path:
153 sys.path.remove(old)
154 sys.path.append(os.path.abspath(new))
155 if not os.path.isdir(new):
156 os.makedirs(new, mode=0777)
157 readme = os.path.join(new, 'README')
158 if not os.path.exists(readme):
159 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
160 shutil.copy(os.path.join(path, 'README'), readme)
161 self.log.debug("IPYTHON_DIR set to: %s" % new)
162
163 def load_config_file(self, suppress_errors=True):
363 164 """Load the config file.
364
365 This tries to load the config file from disk. If successful, the
366 ``CONFIG_FILE`` config variable is set to the resolved config file
367 location. If not successful, an empty config is used.
368
165
369 166 By default, errors in loading config are handled, and a warning
370 167 printed on screen. For testing, the suppress_errors option is set
371 168 to False, so errors will make tests fail.
372 169 """
170 base_config = 'ipython_config.py'
171 self.log.debug("Attempting to load config file: %s" %
172 base_config)
173 try:
174 Application.load_config_file(
175 self,
176 base_config,
177 path=self.config_file_paths
178 )
179 except IOError:
180 # ignore errors loading parent
181 pass
182 if self.config_file_name == base_config:
183 # don't load secondary config
184 return
373 185 self.log.debug("Attempting to load config file: %s" %
374 186 self.config_file_name)
375 loader = PyFileConfigLoader(self.config_file_name,
376 path=self.config_file_paths)
377 187 try:
378 self.file_config = loader.load_config()
379 self.file_config.Global.config_file = loader.full_filename
188 Application.load_config_file(
189 self,
190 self.config_file_name,
191 path=self.config_file_paths
192 )
380 193 except IOError:
381 194 # Only warn if the default config file was NOT being used.
382 if not self.config_file_name==self.default_config_file_name:
195 if self.config_file_specified:
383 196 self.log.warn("Config file not found, skipping: %s" %
384 self.config_file_name, exc_info=True)
385 self.file_config = Config()
197 self.config_file_name)
386 198 except:
387 if not suppress_errors: # For testing purposes
199 # For testing purposes.
200 if not suppress_errors:
388 201 raise
389 202 self.log.warn("Error loading config file: %s" %
390 203 self.config_file_name, exc_info=True)
391 self.file_config = Config()
392 204
393 def set_file_config_log_level(self):
394 # We need to keeep self.log_level updated. But we only use the value
395 # of the file_config if a value was not specified at the command
396 # line, because the command line overrides everything.
397 if not hasattr(self.command_line_config.Global, 'log_level'):
205 def init_profile_dir(self):
206 """initialize the profile dir"""
207 try:
208 # location explicitly specified:
209 location = self.config.ProfileDir.location
210 except AttributeError:
211 # location not specified, find by profile name
398 212 try:
399 self.log_level = self.file_config.Global.log_level
400 except AttributeError:
401 pass # Use existing value
402
403 def post_load_file_config(self):
404 """Do actions after the config file is loaded."""
405 pass
406
407 def log_file_config(self):
408 if hasattr(self.file_config.Global, 'config_file'):
409 self.log.debug("Config file loaded: %s" %
410 self.file_config.Global.config_file)
411 self.log.debug(repr(self.file_config))
412
413 def merge_configs(self):
414 """Merge the default, command line and file config objects."""
415 config = Config()
416 config._merge(self.default_config)
417 config._merge(self.file_config)
418 config._merge(self.command_line_config)
419
420 # XXX fperez - propose to Brian we rename master_config to simply
421 # config, I think this is going to be heavily used in examples and
422 # application code and the name is shorter/easier to find/remember.
423 # For now, just alias it...
424 self.master_config = config
425 self.config = config
426
427 def log_master_config(self):
428 self.log.debug("Master config created:")
429 self.log.debug(repr(self.master_config))
430
431 def pre_construct(self):
432 """Do actions after the config has been built, but before construct."""
433 pass
434
435 def construct(self):
436 """Construct the main objects that make up this app."""
437 self.log.debug("Constructing main objects for application")
438
439 def post_construct(self):
440 """Do actions after construct, but before starting the app."""
441 pass
442
443 def start_app(self):
444 """Actually start the app."""
445 self.log.debug("Starting application")
446
447 #-------------------------------------------------------------------------
448 # Utility methods
449 #-------------------------------------------------------------------------
450
451 def exit(self, exit_status=0):
452 if self._exiting:
453 pass
213 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
214 except ProfileDirError:
215 # not found, maybe create it (always create default profile)
216 if self.auto_create or self.profile=='default':
217 try:
218 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
219 except ProfileDirError:
220 self.log.fatal("Could not create profile: %r"%self.profile)
221 self.exit(1)
222 else:
223 self.log.info("Created profile dir: %r"%p.location)
224 else:
225 self.log.fatal("Profile %r not found."%self.profile)
226 self.exit(1)
227 else:
228 self.log.info("Using existing profile dir: %r"%p.location)
454 229 else:
455 self.log.debug("Exiting application: %s" % self.name)
456 self._exiting = True
457 sys.exit(exit_status)
458
459 def attempt(self, func):
460 try:
461 func()
462 except SystemExit:
463 raise
464 except:
465 self.log.critical("Aborting application: %s" % self.name,
466 exc_info=True)
467 self.exit(0)
230 # location is fully specified
231 try:
232 p = ProfileDir.find_profile_dir(location, self.config)
233 except ProfileDirError:
234 # not found, maybe create it
235 if self.auto_create:
236 try:
237 p = ProfileDir.create_profile_dir(location, self.config)
238 except ProfileDirError:
239 self.log.fatal("Could not create profile directory: %r"%location)
240 self.exit(1)
241 else:
242 self.log.info("Creating new profile dir: %r"%location)
243 else:
244 self.log.fatal("Profile directory %r not found."%location)
245 self.exit(1)
246 else:
247 self.log.info("Using existing profile dir: %r"%location)
248
249 self.profile_dir = p
250 self.config_file_paths.append(p.location)
251
252 def init_config_files(self):
253 """[optionally] copy default config files into profile dir."""
254 # copy config files
255 if self.copy_config_files:
256 path = self.builtin_profile_dir
257 src = self.profile
258
259 cfg = self.config_file_name
260 if path and os.path.exists(os.path.join(path, cfg)):
261 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
262 cfg, src, self.profile_dir.location, self.overwrite)
263 )
264 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
265 else:
266 self.stage_default_config_file()
267
268 def stage_default_config_file(self):
269 """auto generate default config file, and stage it into the profile."""
270 s = self.generate_config_file()
271 fname = os.path.join(self.profile_dir.location, self.config_file_name)
272 if self.overwrite or not os.path.exists(fname):
273 self.log.warn("Generating default config file: %r"%(fname))
274 with open(fname, 'w') as f:
275 f.write(s)
276
277
278 def initialize(self, argv=None):
279 self.init_crash_handler()
280 self.parse_command_line(argv)
281 if self.subapp is not None:
282 # stop here if subapp is taking over
283 return
284 cl_config = self.config
285 self.init_profile_dir()
286 self.init_config_files()
287 self.load_config_file()
288 # enforce cl-opts override configfile opts:
289 self.update_config(cl_config)
468 290
@@ -101,18 +101,15 b' class HistoryManager(Configurable):'
101 101
102 102 if self.hist_file == u'':
103 103 # No one has set the hist_file, yet.
104 if shell.profile:
105 histfname = 'history-%s' % shell.profile
106 else:
107 histfname = 'history'
108 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
104 histfname = 'history'
105 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
109 106
110 107 try:
111 108 self.init_db()
112 109 except sqlite3.DatabaseError:
113 110 if os.path.isfile(self.hist_file):
114 111 # Try to move the file out of the way.
115 newpath = os.path.join(self.shell.ipython_dir, "hist-corrupt.sqlite")
112 newpath = os.path.join(self.shell.profile_dir.location, "hist-corrupt.sqlite")
116 113 os.rename(self.hist_file, newpath)
117 114 print("ERROR! History file wasn't a valid SQLite database.",
118 115 "It was moved to %s" % newpath, "and a new file created.")
@@ -57,6 +57,7 b' from IPython.core.magic import Magic'
57 57 from IPython.core.payload import PayloadManager
58 58 from IPython.core.plugin import PluginManager
59 59 from IPython.core.prefilter import PrefilterManager, ESC_MAGIC
60 from IPython.core.profiledir import ProfileDir
60 61 from IPython.external.Itpl import ItplNS
61 62 from IPython.utils import PyColorize
62 63 from IPython.utils import io
@@ -238,7 +239,9 b' class InteractiveShell(SingletonConfigurable, Magic):'
238 239 """
239 240 )
240 241 colors = CaselessStrEnum(('NoColor','LightBG','Linux'),
241 default_value=get_default_colors(), config=True)
242 default_value=get_default_colors(), config=True,
243 help="Set the color scheme (NoColor, Linux, or LightBG)."
244 )
242 245 debug = CBool(False, config=True)
243 246 deep_reload = CBool(False, config=True, help=
244 247 """
@@ -291,7 +294,6 b' class InteractiveShell(SingletonConfigurable, Magic):'
291 294 """
292 295 )
293 296
294 profile = Unicode('', config=True)
295 297 prompt_in1 = Str('In [\\#]: ', config=True)
296 298 prompt_in2 = Str(' .\\D.: ', config=True)
297 299 prompt_out = Str('Out[\\#]: ', config=True)
@@ -342,10 +344,18 b' class InteractiveShell(SingletonConfigurable, Magic):'
342 344 payload_manager = Instance('IPython.core.payload.PayloadManager')
343 345 history_manager = Instance('IPython.core.history.HistoryManager')
344 346
347 profile_dir = Instance('IPython.core.application.ProfileDir')
348 @property
349 def profile(self):
350 if self.profile_dir is not None:
351 name = os.path.basename(self.profile_dir.location)
352 return name.replace('profile_','')
353
354
345 355 # Private interface
346 356 _post_execute = Instance(dict)
347 357
348 def __init__(self, config=None, ipython_dir=None,
358 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
349 359 user_ns=None, user_global_ns=None,
350 360 custom_exceptions=((), None)):
351 361
@@ -355,6 +365,7 b' class InteractiveShell(SingletonConfigurable, Magic):'
355 365
356 366 # These are relatively independent and stateless
357 367 self.init_ipython_dir(ipython_dir)
368 self.init_profile_dir(profile_dir)
358 369 self.init_instance_attrs()
359 370 self.init_environment()
360 371
@@ -372,7 +383,7 b' class InteractiveShell(SingletonConfigurable, Magic):'
372 383 # While we're trying to have each part of the code directly access what
373 384 # it needs without keeping redundant references to objects, we have too
374 385 # much legacy code that expects ip.db to exist.
375 self.db = PickleShareDB(os.path.join(self.ipython_dir, 'db'))
386 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
376 387
377 388 self.init_history()
378 389 self.init_encoding()
@@ -457,16 +468,16 b' class InteractiveShell(SingletonConfigurable, Magic):'
457 468 def init_ipython_dir(self, ipython_dir):
458 469 if ipython_dir is not None:
459 470 self.ipython_dir = ipython_dir
460 self.config.Global.ipython_dir = self.ipython_dir
461 471 return
462 472
463 if hasattr(self.config.Global, 'ipython_dir'):
464 self.ipython_dir = self.config.Global.ipython_dir
465 else:
466 self.ipython_dir = get_ipython_dir()
473 self.ipython_dir = get_ipython_dir()
467 474
468 # All children can just read this
469 self.config.Global.ipython_dir = self.ipython_dir
475 def init_profile_dir(self, profile_dir):
476 if profile_dir is not None:
477 self.profile_dir = profile_dir
478 return
479 self.profile_dir =\
480 ProfileDir.create_profile_dir_by_name(self.ipython_dir, 'default')
470 481
471 482 def init_instance_attrs(self):
472 483 self.more = False
@@ -46,6 +46,7 b' from IPython.core import debugger, oinspect'
46 46 from IPython.core.error import TryNext
47 47 from IPython.core.error import UsageError
48 48 from IPython.core.fakemodule import FakeModule
49 from IPython.core.profiledir import ProfileDir
49 50 from IPython.core.macro import Macro
50 51 from IPython.core import page
51 52 from IPython.core.prefilter import ESC_MAGIC
@@ -533,10 +534,7 b' Currently the magic system has the following functions:\\n"""'
533 534
534 535 def magic_profile(self, parameter_s=''):
535 536 """Print your currently active IPython profile."""
536 if self.shell.profile:
537 printpl('Current IPython profile: $self.shell.profile.')
538 else:
539 print 'No profile active.'
537 print self.shell.profile
540 538
541 539 def magic_pinfo(self, parameter_s='', namespaces=None):
542 540 """Provide detailed information about an object.
@@ -3373,22 +3371,16 b' Defaulting color scheme to \'NoColor\'"""'
3373 3371 else:
3374 3372 overwrite = False
3375 3373 from IPython.config import profile
3376 profile_dir = os.path.split(profile.__file__)[0]
3374 profile_dir = os.path.dirname(profile.__file__)
3377 3375 ipython_dir = self.ipython_dir
3378 files = os.listdir(profile_dir)
3379
3380 to_install = []
3381 for f in files:
3382 if f.startswith('ipython_config'):
3383 src = os.path.join(profile_dir, f)
3384 dst = os.path.join(ipython_dir, f)
3385 if (not os.path.isfile(dst)) or overwrite:
3386 to_install.append((f, src, dst))
3387 if len(to_install)>0:
3388 print "Installing profiles to: ", ipython_dir
3389 for (f, src, dst) in to_install:
3390 shutil.copy(src, dst)
3391 print " %s" % f
3376 print "Installing profiles to: %s [overwrite=%s]"%(ipython_dir,overwrite)
3377 for src in os.listdir(profile_dir):
3378 if src.startswith('profile_'):
3379 name = src.replace('profile_', '')
3380 print " %s"%name
3381 pd = ProfileDir.create_profile_dir_by_name(ipython_dir, name)
3382 pd.copy_config_file('ipython_config.py', path=src,
3383 overwrite=overwrite)
3392 3384
3393 3385 @skip_doctest
3394 3386 def magic_install_default_config(self, s):
@@ -3404,15 +3396,9 b' Defaulting color scheme to \'NoColor\'"""'
3404 3396 overwrite = True
3405 3397 else:
3406 3398 overwrite = False
3407 from IPython.config import default
3408 config_dir = os.path.split(default.__file__)[0]
3409 ipython_dir = self.ipython_dir
3410 default_config_file_name = 'ipython_config.py'
3411 src = os.path.join(config_dir, default_config_file_name)
3412 dst = os.path.join(ipython_dir, default_config_file_name)
3413 if (not os.path.isfile(dst)) or overwrite:
3414 shutil.copy(src, dst)
3415 print "Installing default config file: %s" % dst
3399 pd = self.shell.profile_dir
3400 print "Installing default config file in: %s" % pd.location
3401 pd.copy_config_file('ipython_config.py', overwrite=overwrite)
3416 3402
3417 3403 # Pylab support: simple wrappers that activate pylab, load gui input
3418 3404 # handling and modify slightly %run
@@ -4,7 +4,7 b''
4 4 import os
5 5 import tempfile
6 6
7 from IPython.core.application import Application
7 from IPython.core.application import BaseIPythonApplication
8 8 from IPython.testing import decorators as testdec
9 9
10 10 @testdec.onlyif_unicode_paths
@@ -16,22 +16,11 b' def test_unicode_cwd():'
16 16 os.chdir(wd)
17 17 #raise Exception(repr(os.getcwd()))
18 18 try:
19 app = Application()
19 app = BaseIPythonApplication()
20 20 # The lines below are copied from Application.initialize()
21 app.create_default_config()
22 app.log_default_config()
23 app.set_default_config_log_level()
24
25 # Find resources needed for filesystem access, using information from
26 # the above two
27 app.find_ipython_dir()
28 app.find_resources()
29 app.find_config_file_name()
30 app.find_config_file_paths()
31
32 # File-based config
33 app.pre_load_file_config()
34 app.load_file_config(suppress_errors=False)
21 app.init_profile_dir()
22 app.init_config_files()
23 app.load_config_file(suppress_errors=False)
35 24 finally:
36 25 os.chdir(old_wd)
37 26
@@ -48,22 +37,11 b' def test_unicode_ipdir():'
48 37 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
49 38 os.environ["IPYTHONDIR"] = ipdir.encode("utf-8")
50 39 try:
51 app = Application()
40 app = BaseIPythonApplication()
52 41 # The lines below are copied from Application.initialize()
53 app.create_default_config()
54 app.log_default_config()
55 app.set_default_config_log_level()
56
57 # Find resources needed for filesystem access, using information from
58 # the above two
59 app.find_ipython_dir()
60 app.find_resources()
61 app.find_config_file_name()
62 app.find_config_file_paths()
63
64 # File-based config
65 app.pre_load_file_config()
66 app.load_file_config(suppress_errors=False)
42 app.init_profile_dir()
43 app.init_config_files()
44 app.load_config_file(suppress_errors=False)
67 45 finally:
68 46 if old_ipdir1:
69 47 os.environ["IPYTHONDIR"] = old_ipdir1
@@ -92,7 +92,7 b' class ParalleMagic(Plugin):'
92 92 Then you can do the following::
93 93
94 94 In [24]: %px a = 5
95 Parallel execution on engines: all
95 Parallel execution on engine(s): all
96 96 Out[24]:
97 97 <Results List>
98 98 [0] In [7]: a = 5
@@ -102,7 +102,7 b' class ParalleMagic(Plugin):'
102 102 if self.active_view is None:
103 103 print NO_ACTIVE_VIEW
104 104 return
105 print "Parallel execution on engines: %s" % self.active_view.targets
105 print "Parallel execution on engine(s): %s" % self.active_view.targets
106 106 result = self.active_view.execute(parameter_s, block=False)
107 107 if self.active_view.block:
108 108 result.get()
@@ -125,9 +125,9 b' class ParalleMagic(Plugin):'
125 125 %autopx to enabled
126 126
127 127 In [26]: a = 10
128 Parallel execution on engines: [0,1,2,3]
128 Parallel execution on engine(s): [0,1,2,3]
129 129 In [27]: print a
130 Parallel execution on engines: [0,1,2,3]
130 Parallel execution on engine(s): [0,1,2,3]
131 131 [stdout:0] 10
132 132 [stdout:1] 10
133 133 [stdout:2] 10
@@ -174,15 +174,21 b' class ParalleMagic(Plugin):'
174 174 If self.active_view.block is True, wait for the result
175 175 and display the result. Otherwise, this is a noop.
176 176 """
177 if isinstance(result.stdout, basestring):
178 # single result
179 stdouts = [result.stdout.rstrip()]
180 else:
181 stdouts = [s.rstrip() for s in result.stdout]
182
177 183 targets = self.active_view.targets
178 184 if isinstance(targets, int):
179 185 targets = [targets]
180 if targets == 'all':
186 elif targets == 'all':
181 187 targets = self.active_view.client.ids
182 stdout = [s.rstrip() for s in result.stdout]
183 if any(stdout):
184 for i,eid in enumerate(targets):
185 print '[stdout:%i]'%eid, stdout[i]
188
189 if any(stdouts):
190 for eid,stdout in zip(targets, stdouts):
191 print '[stdout:%i]'%eid, stdout
186 192
187 193
188 194 def pxrun_cell(self, raw_cell, store_history=True):
@@ -30,8 +30,8 b' class BaseFrontendMixin(object):'
30 30
31 31 # Disconnect the old kernel manager's channels.
32 32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.disconnect(self._dispatch)
33 old_manager.shell_channel.message_received.disconnect(self._dispatch)
34 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
35 35 old_manager.hb_channel.kernel_died.disconnect(
36 36 self._handle_kernel_died)
37 37
@@ -50,8 +50,8 b' class BaseFrontendMixin(object):'
50 50
51 51 # Connect the new kernel manager's channels.
52 52 kernel_manager.sub_channel.message_received.connect(self._dispatch)
53 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
54 kernel_manager.rep_channel.message_received.connect(self._dispatch)
53 kernel_manager.shell_channel.message_received.connect(self._dispatch)
54 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
55 55 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
56 56
57 57 # Handle the case where the kernel manager started channels before
@@ -19,7 +19,7 b' from IPython.external.qt import QtCore, QtGui'
19 19 from IPython.config.configurable import Configurable
20 20 from IPython.frontend.qt.rich_text import HtmlExporter
21 21 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
22 from IPython.utils.traitlets import Bool, Enum, Int
22 from IPython.utils.traitlets import Bool, Enum, Int, Unicode
23 23 from ansi_code_processor import QtAnsiCodeProcessor
24 24 from completion_widget import CompletionWidget
25 25 from kill_ring import QtKillRing
@@ -55,33 +55,62 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
55 55
56 56 #------ Configuration ------------------------------------------------------
57 57
58 # Whether to process ANSI escape codes.
59 ansi_codes = Bool(True, config=True)
60
61 # The maximum number of lines of text before truncation. Specifying a
62 # non-positive number disables text truncation (not recommended).
63 buffer_size = Int(500, config=True)
64
65 # Whether to use a list widget or plain text output for tab completion.
66 gui_completion = Bool(False, config=True)
67
68 # The type of underlying text widget to use. Valid values are 'plain', which
69 # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
58 ansi_codes = Bool(True, config=True,
59 help="Whether to process ANSI escape codes."
60 )
61 buffer_size = Int(500, config=True,
62 help="""
63 The maximum number of lines of text before truncation. Specifying a
64 non-positive number disables text truncation (not recommended).
65 """
66 )
67 gui_completion = Bool(False, config=True,
68 help="Use a list widget instead of plain text output for tab completion."
69 )
70 70 # NOTE: this value can only be specified during initialization.
71 kind = Enum(['plain', 'rich'], default_value='plain', config=True)
72
73 # The type of paging to use. Valid values are:
74 # 'inside' : The widget pages like a traditional terminal.
75 # 'hsplit' : When paging is requested, the widget is split
76 # horizontally. The top pane contains the console, and the
77 # bottom pane contains the paged text.
78 # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
79 # 'custom' : No action is taken by the widget beyond emitting a
80 # 'custom_page_requested(str)' signal.
81 # 'none' : The text is written directly to the console.
71 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
72 help="""
73 The type of underlying text widget to use. Valid values are 'plain', which
74 specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
75 """
76 )
82 77 # NOTE: this value can only be specified during initialization.
83 78 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
84 default_value='inside', config=True)
79 default_value='inside', config=True,
80 help="""
81 The type of paging to use. Valid values are:
82
83 'inside' : The widget pages like a traditional terminal.
84 'hsplit' : When paging is requested, the widget is split
85 horizontally. The top pane contains the console, and the
86 bottom pane contains the paged text.
87 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
88 'custom' : No action is taken by the widget beyond emitting a
89 'custom_page_requested(str)' signal.
90 'none' : The text is written directly to the console.
91 """)
92
93 font_family = Unicode(config=True,
94 help="""The font family to use for the console.
95 On OSX this defaults to Monaco, on Windows the default is
96 Consolas with fallback of Courier, and on other platforms
97 the default is Monospace.
98 """)
99 def _font_family_default(self):
100 if sys.platform == 'win32':
101 # Consolas ships with Vista/Win7, fallback to Courier if needed
102 return 'Consolas'
103 elif sys.platform == 'darwin':
104 # OSX always has Monaco, no need for a fallback
105 return 'Monaco'
106 else:
107 # Monospace should always exist, no need for a fallback
108 return 'Monospace'
109
110 font_size = Int(config=True,
111 help="""The font size. If unconfigured, Qt will be entrusted
112 with the size of the font.
113 """)
85 114
86 115 # Whether to override ShortcutEvents for the keybindings defined by this
87 116 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
@@ -591,16 +620,18 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
591 620 """
592 621 if sys.platform == 'win32':
593 622 # Consolas ships with Vista/Win7, fallback to Courier if needed
594 family, fallback = 'Consolas', 'Courier'
623 fallback = 'Courier'
595 624 elif sys.platform == 'darwin':
596 # OSX always has Monaco, no need for a fallback
597 family, fallback = 'Monaco', None
625 # OSX always has Monaco
626 fallback = 'Monaco'
627 else:
628 # Monospace should always exist
629 fallback = 'Monospace'
630 font = get_font(self.font_family, fallback)
631 if self.font_size:
632 font.setPointSize(self.font_size)
598 633 else:
599 # FIXME: remove Consolas as a default on Linux once our font
600 # selections are configurable by the user.
601 family, fallback = 'Consolas', 'Monospace'
602 font = get_font(family, fallback)
603 font.setPointSize(QtGui.qApp.font().pointSize())
634 font.setPointSize(QtGui.qApp.font().pointSize())
604 635 font.setStyleHint(QtGui.QFont.TypeWriter)
605 636 self._set_font(font)
606 637
@@ -13,7 +13,7 b' from IPython.external.qt import QtCore, QtGui'
13 13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 14 from IPython.core.oinspect import call_tip
15 15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool
16 from IPython.utils.traitlets import Bool, Instance
17 17 from bracket_matcher import BracketMatcher
18 18 from call_tip_widget import CallTipWidget
19 19 from completion_lexer import CompletionLexer
@@ -106,6 +106,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
106 106 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
107 107 _input_splitter_class = InputSplitter
108 108 _local_kernel = False
109 _highlighter = Instance(FrontendHighlighter)
109 110
110 111 #---------------------------------------------------------------------------
111 112 # 'object' interface
@@ -183,7 +184,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
183 184
184 185 See parent class :meth:`execute` docstring for full details.
185 186 """
186 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
187 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
187 188 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
188 189 self._hidden = hidden
189 190 if not hidden:
@@ -329,7 +330,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
329 330 self.kernel_manager.sub_channel.flush()
330 331
331 332 def callback(line):
332 self.kernel_manager.rep_channel.input(line)
333 self.kernel_manager.stdin_channel.input(line)
333 334 self._readline(msg['content']['prompt'], callback=callback)
334 335
335 336 def _handle_kernel_died(self, since_last_heartbeat):
@@ -526,7 +527,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
526 527
527 528 # Send the metadata request to the kernel
528 529 name = '.'.join(context)
529 msg_id = self.kernel_manager.xreq_channel.object_info(name)
530 msg_id = self.kernel_manager.shell_channel.object_info(name)
530 531 pos = self._get_cursor().position()
531 532 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
532 533 return True
@@ -537,7 +538,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
537 538 context = self._get_context()
538 539 if context:
539 540 # Send the completion request to the kernel
540 msg_id = self.kernel_manager.xreq_channel.complete(
541 msg_id = self.kernel_manager.shell_channel.complete(
541 542 '.'.join(context), # text
542 543 self._get_input_buffer_cursor_line(), # line
543 544 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -23,9 +23,7 b' from IPython.core.inputsplitter import IPythonInputSplitter, \\'
23 23 from IPython.core.usage import default_gui_banner
24 24 from IPython.utils.traitlets import Bool, Str, Unicode
25 25 from frontend_widget import FrontendWidget
26 from styles import (default_light_style_sheet, default_light_syntax_style,
27 default_dark_style_sheet, default_dark_syntax_style,
28 default_bw_style_sheet, default_bw_syntax_style)
26 import styles
29 27
30 28 #-----------------------------------------------------------------------------
31 29 # Constants
@@ -42,6 +40,11 b" default_output_sep2 = ''"
42 40 # Base path for most payload sources.
43 41 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
44 42
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
45 else:
46 default_editor = ''
47
45 48 #-----------------------------------------------------------------------------
46 49 # IPythonWidget class
47 50 #-----------------------------------------------------------------------------
@@ -56,26 +59,35 b' class IPythonWidget(FrontendWidget):'
56 59 custom_edit = Bool(False)
57 60 custom_edit_requested = QtCore.Signal(object, object)
58 61
59 # A command for invoking a system text editor. If the string contains a
60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 # be appended to the end the command.
62 editor = Unicode('default', config=True)
63
64 # The editor command to use when a specific line number is requested. The
65 # string should contain two format specifiers: {line} and {filename}. If
66 # this parameter is not specified, the line number option to the %edit magic
67 # will be ignored.
68 editor_line = Unicode(config=True)
69
70 # A CSS stylesheet. The stylesheet can contain classes for:
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 style_sheet = Unicode(config=True)
62 editor = Unicode(default_editor, config=True,
63 help="""
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename will
66 be appended to the end the command.
67 """)
68
69 editor_line = Unicode(config=True,
70 help="""
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit magic
74 will be ignored.
75 """)
76
77 style_sheet = Unicode(config=True,
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
75 84
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 # the style sheet is queried for Pygments style information.
78 syntax_style = Str(config=True)
85
86 syntax_style = Str(config=True,
87 help="""
88 If not empty, use this Pygments style for syntax highlighting. Otherwise,
89 the style sheet is queried for Pygments style information.
90 """)
79 91
80 92 # Prompts.
81 93 in_prompt = Str(default_in_prompt, config=True)
@@ -215,7 +227,7 b' class IPythonWidget(FrontendWidget):'
215 227 """ Reimplemented to make a history request.
216 228 """
217 229 super(IPythonWidget, self)._started_channels()
218 self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
230 self.kernel_manager.shell_channel.history(hist_access_type='tail', n=1000)
219 231
220 232 #---------------------------------------------------------------------------
221 233 # 'ConsoleWidget' public interface
@@ -257,7 +269,7 b' class IPythonWidget(FrontendWidget):'
257 269 text = ''
258 270
259 271 # Send the completion request to the kernel
260 msg_id = self.kernel_manager.xreq_channel.complete(
272 msg_id = self.kernel_manager.shell_channel.complete(
261 273 text, # text
262 274 self._get_input_buffer_cursor_line(), # line
263 275 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -308,7 +320,7 b' class IPythonWidget(FrontendWidget):'
308 320 """
309 321 # If a number was not specified, make a prompt number request.
310 322 if number is None:
311 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
323 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
312 324 info = self._ExecutionRequest(msg_id, 'prompt')
313 325 self._request_info['execute'] = info
314 326 return
@@ -371,14 +383,14 b' class IPythonWidget(FrontendWidget):'
371 383 """
372 384 colors = colors.lower()
373 385 if colors=='lightbg':
374 self.style_sheet = default_light_style_sheet
375 self.syntax_style = default_light_syntax_style
386 self.style_sheet = styles.default_light_style_sheet
387 self.syntax_style = styles.default_light_syntax_style
376 388 elif colors=='linux':
377 self.style_sheet = default_dark_style_sheet
378 self.syntax_style = default_dark_syntax_style
389 self.style_sheet = styles.default_dark_style_sheet
390 self.syntax_style = styles.default_dark_syntax_style
379 391 elif colors=='nocolor':
380 self.style_sheet = default_bw_style_sheet
381 self.syntax_style = default_bw_syntax_style
392 self.style_sheet = styles.default_bw_style_sheet
393 self.syntax_style = styles.default_bw_syntax_style
382 394 else:
383 395 raise KeyError("No such color scheme: %s"%colors)
384 396
@@ -399,8 +411,10 b' class IPythonWidget(FrontendWidget):'
399 411 """
400 412 if self.custom_edit:
401 413 self.custom_edit_requested.emit(filename, line)
402 elif self.editor == 'default':
403 self._append_plain_text('No default editor available.\n')
414 elif not self.editor:
415 self._append_plain_text('No default editor available.\n'
416 'Specify a GUI text editor in the `IPythonWidget.editor` configurable\n'
417 'to enable the %edit magic')
404 418 else:
405 419 try:
406 420 filename = '"%s"' % filename
@@ -482,9 +496,13 b' class IPythonWidget(FrontendWidget):'
482 496 bg_color = self._control.palette().window().color()
483 497 self._ansi_processor.set_background_color(bg_color)
484 498
499
485 500 def _syntax_style_changed(self):
486 501 """ Set the style for the syntax highlighter.
487 502 """
503 if self._highlighter is None:
504 # ignore premature calls
505 return
488 506 if self.syntax_style:
489 507 self._highlighter.set_style(self.syntax_style)
490 508 else:
@@ -1,21 +1,50 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2
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.
5
6 Authors:
7
8 * Evan Patterson
9 * Min RK
10 * Erik Tollerud
11 * Fernando Perez
12
2 13 """
3 14
4 15 #-----------------------------------------------------------------------------
5 16 # Imports
6 17 #-----------------------------------------------------------------------------
7 18
8 # Systemm library imports
19 # stdlib imports
20 import os
21 import signal
22 import sys
23
24 # System library imports
9 25 from IPython.external.qt import QtGui
10 26 from pygments.styles import get_all_styles
11 27
12 28 # Local imports
13 from IPython.external.argparse import ArgumentParser
29 from IPython.config.application import boolean_flag
30 from IPython.core.application import BaseIPythonApplication
31 from IPython.core.profiledir import ProfileDir
14 32 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
15 33 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
16 34 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
17 35 from IPython.frontend.qt.console import styles
18 36 from IPython.frontend.qt.kernelmanager import QtKernelManager
37 from IPython.utils.traitlets import (
38 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
39 )
40 from IPython.zmq.ipkernel import (
41 flags as ipkernel_flags,
42 aliases as ipkernel_aliases,
43 IPKernelApp
44 )
45 from IPython.zmq.session import Session
46 from IPython.zmq.zmqshell import ZMQInteractiveShell
47
19 48
20 49 #-----------------------------------------------------------------------------
21 50 # Network Constants
@@ -33,7 +62,8 b' class MainWindow(QtGui.QMainWindow):'
33 62 # 'object' interface
34 63 #---------------------------------------------------------------------------
35 64
36 def __init__(self, app, frontend, existing=False, may_close=True):
65 def __init__(self, app, frontend, existing=False, may_close=True,
66 confirm_exit=True):
37 67 """ Create a MainWindow for the specified FrontendWidget.
38 68
39 69 The app is passed as an argument to allow for different
@@ -52,6 +82,7 b' class MainWindow(QtGui.QMainWindow):'
52 82 else:
53 83 self._may_close = True
54 84 self._frontend.exit_requested.connect(self.close)
85 self._confirm_exit = confirm_exit
55 86 self.setCentralWidget(frontend)
56 87
57 88 #---------------------------------------------------------------------------
@@ -71,6 +102,11 b' class MainWindow(QtGui.QMainWindow):'
71 102
72 103 kernel_manager = self._frontend.kernel_manager
73 104
105 if keepkernel is None and not self._confirm_exit:
106 # don't prompt, just terminate the kernel if we own it
107 # or leave it alone if we don't
108 keepkernel = not self._existing
109
74 110 if keepkernel is None: #show prompt
75 111 if kernel_manager and kernel_manager.channels_running:
76 112 title = self.window().windowTitle()
@@ -127,123 +163,216 b' class MainWindow(QtGui.QMainWindow):'
127 163 event.accept()
128 164
129 165 #-----------------------------------------------------------------------------
130 # Main entry point
166 # Aliases and Flags
131 167 #-----------------------------------------------------------------------------
132 168
133 def main():
134 """ Entry point for application.
169 flags = dict(ipkernel_flags)
170
171 flags.update({
172 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
173 "Connect to an existing kernel."),
174 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
175 "Use a pure Python kernel instead of an IPython kernel."),
176 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
177 "Disable rich text support."),
178 })
179 flags.update(boolean_flag(
180 'gui-completion', 'ConsoleWidget.gui_completion',
181 "use a GUI widget for tab completion",
182 "use plaintext output for completion"
183 ))
184 flags.update(boolean_flag(
185 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
186 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.
188 """,
189 """Don't prompt the user when exiting. This will terminate the kernel
190 if it is owned by the frontend, and leave it alive if it is external.
135 191 """
136 # Parse command line arguments.
137 parser = ArgumentParser()
138 kgroup = parser.add_argument_group('kernel options')
139 kgroup.add_argument('-e', '--existing', action='store_true',
140 help='connect to an existing kernel')
141 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
142 help=\
143 "set the kernel\'s IP address [default localhost].\
144 If the IP address is something other than localhost, then \
145 Consoles on other machines will be able to connect\
146 to the Kernel, so be careful!")
147 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
148 help='set the XREQ channel port [default random]')
149 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
150 help='set the SUB channel port [default random]')
151 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
152 help='set the REP channel port [default random]')
153 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
154 help='set the heartbeat port [default random]')
155
156 egroup = kgroup.add_mutually_exclusive_group()
157 egroup.add_argument('--pure', action='store_true', help = \
158 'use a pure Python kernel instead of an IPython kernel')
159 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
160 const='auto', help = \
161 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
162 given, the GUI backend is matplotlib's, otherwise use one of: \
163 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
164
165 wgroup = parser.add_argument_group('widget options')
166 wgroup.add_argument('--paging', type=str, default='inside',
167 choices = ['inside', 'hsplit', 'vsplit', 'none'],
168 help='set the paging style [default inside]')
169 wgroup.add_argument('--plain', action='store_true',
170 help='disable rich text support')
171 wgroup.add_argument('--gui-completion', action='store_true',
172 help='use a GUI widget for tab completion')
173 wgroup.add_argument('--style', type=str,
174 choices = list(get_all_styles()),
175 help='specify a pygments style for by name')
176 wgroup.add_argument('--stylesheet', type=str,
177 help='path to a custom CSS stylesheet')
178 wgroup.add_argument('--colors', type=str, help = \
179 "Set the color scheme (LightBG,Linux,NoColor). This is guessed \
180 based on the pygments style if not set.")
181
182 args = parser.parse_args()
183
184 # parse the colors arg down to current known labels
185 if args.colors:
186 colors=args.colors.lower()
187 if colors in ('lightbg', 'light'):
188 colors='lightbg'
189 elif colors in ('dark', 'linux'):
190 colors='linux'
191 else:
192 colors='nocolor'
193 elif args.style:
194 if args.style=='bw':
195 colors='nocolor'
196 elif styles.dark_style(args.style):
197 colors='linux'
192 ))
193 # the flags that are specific to the frontend
194 # these must be scrubbed before being passed to the kernel,
195 # or it will raise an error on unrecognized flags
196 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
197 'confirm-exit', 'no-confirm-exit']
198
199 aliases = dict(ipkernel_aliases)
200
201 aliases.update(dict(
202 hb = 'IPythonQtConsoleApp.hb_port',
203 shell = 'IPythonQtConsoleApp.shell_port',
204 iopub = 'IPythonQtConsoleApp.iopub_port',
205 stdin = 'IPythonQtConsoleApp.stdin_port',
206 ip = 'IPythonQtConsoleApp.ip',
207
208 plain = 'IPythonQtConsoleApp.plain',
209 pure = 'IPythonQtConsoleApp.pure',
210 gui_completion = 'ConsoleWidget.gui_completion',
211 style = 'IPythonWidget.syntax_style',
212 stylesheet = 'IPythonQtConsoleApp.stylesheet',
213 colors = 'ZMQInteractiveShell.colors',
214
215 editor = 'IPythonWidget.editor',
216 ))
217
218 #-----------------------------------------------------------------------------
219 # IPythonQtConsole
220 #-----------------------------------------------------------------------------
221 class IPythonQtConsoleApp(BaseIPythonApplication):
222 name = 'ipython-qtconsole'
223 default_config_file_name='ipython_config.py'
224
225 description = """
226 The IPython QtConsole.
227
228 This launches a Console-style application using Qt. It is not a full
229 console, in that launched terminal subprocesses will not.
230
231 The QtConsole supports various extra features beyond the
232
233 """
234
235 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
236 flags = Dict(flags)
237 aliases = Dict(aliases)
238
239 kernel_argv = List(Unicode)
240
241 # connection info:
242 ip = Unicode(LOCALHOST, config=True,
243 help="""Set the kernel\'s IP address [default localhost].
244 If the IP address is something other than localhost, then
245 Consoles on other machines will be able to connect
246 to the Kernel, so be careful!"""
247 )
248 hb_port = Int(0, config=True,
249 help="set the heartbeat port [default: random]")
250 shell_port = Int(0, config=True,
251 help="set the shell (XREP) port [default: random]")
252 iopub_port = Int(0, config=True,
253 help="set the iopub (PUB) port [default: random]")
254 stdin_port = Int(0, config=True,
255 help="set the stdin (XREQ) port [default: random]")
256
257 existing = CBool(False, config=True,
258 help="Whether to connect to an already running Kernel.")
259
260 stylesheet = Unicode('', config=True,
261 help="path to a custom CSS stylesheet")
262
263 pure = CBool(False, config=True,
264 help="Use a pure Python kernel instead of an IPython kernel.")
265 plain = CBool(False, config=True,
266 help="Use a plaintext widget instead of rich text (plain can't print/save).")
267
268 def _pure_changed(self, name, old, new):
269 kind = 'plain' if self.plain else 'rich'
270 self.config.ConsoleWidget.kind = kind
271 if self.pure:
272 self.widget_factory = FrontendWidget
273 elif self.plain:
274 self.widget_factory = IPythonWidget
198 275 else:
199 colors='lightbg'
200 else:
201 colors=None
202
203 # Don't let Qt or ZMQ swallow KeyboardInterupts.
204 import signal
205 signal.signal(signal.SIGINT, signal.SIG_DFL)
206
207 # Create a KernelManager and start a kernel.
208 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
209 sub_address=(args.ip, args.sub),
210 rep_address=(args.ip, args.rep),
211 hb_address=(args.ip, args.hb))
212 if not args.existing:
213 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
214 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
215
216 kwargs = dict(ip=args.ip)
217 if args.pure:
218 kwargs['ipython']=False
276 self.widget_factory = RichIPythonWidget
277
278 _plain_changed = _pure_changed
279
280 confirm_exit = CBool(True, config=True,
281 help="""
282 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
283 to force a direct exit without any confirmation.""",
284 )
285
286 # the factory for creating a widget
287 widget_factory = Any(RichIPythonWidget)
288
289 def parse_command_line(self, argv=None):
290 super(IPythonQtConsoleApp, self).parse_command_line(argv)
291 if argv is None:
292 argv = sys.argv[1:]
293
294 self.kernel_argv = list(argv) # copy
295
296 # scrub frontend-specific flags
297 for a in argv:
298 if a.startswith('--') and a[2:] in qt_flags:
299 self.kernel_argv.remove(a)
300
301 def init_kernel_manager(self):
302 # Don't let Qt or ZMQ swallow KeyboardInterupts.
303 signal.signal(signal.SIGINT, signal.SIG_DFL)
304
305 # Create a KernelManager and start a kernel.
306 self.kernel_manager = QtKernelManager(
307 shell_address=(self.ip, self.shell_port),
308 sub_address=(self.ip, self.iopub_port),
309 stdin_address=(self.ip, self.stdin_port),
310 hb_address=(self.ip, self.hb_port),
311 config=self.config
312 )
313 # start the kernel
314 if not self.existing:
315 kwargs = dict(ip=self.ip, ipython=not self.pure)
316 kwargs['extra_arguments'] = self.kernel_argv
317 self.kernel_manager.start_kernel(**kwargs)
318 self.kernel_manager.start_channels()
319
320
321 def init_qt_elements(self):
322 # Create the widget.
323 self.app = QtGui.QApplication([])
324 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
325 self.widget = self.widget_factory(config=self.config,
326 local_kernel=local_kernel)
327 self.widget.kernel_manager = self.kernel_manager
328 self.window = MainWindow(self.app, self.widget, self.existing,
329 may_close=local_kernel,
330 confirm_exit=self.confirm_exit)
331 self.window.setWindowTitle('Python' if self.pure else 'IPython')
332
333 def init_colors(self):
334 """Configure the coloring of the widget"""
335 # Note: This will be dramatically simplified when colors
336 # are removed from the backend.
337
338 if self.pure:
339 # only IPythonWidget supports styling
340 return
341
342 # parse the colors arg down to current known labels
343 try:
344 colors = self.config.ZMQInteractiveShell.colors
345 except AttributeError:
346 colors = None
347 try:
348 style = self.config.IPythonWidget.colors
349 except AttributeError:
350 style = None
351
352 # find the value for colors:
353 if colors:
354 colors=colors.lower()
355 if colors in ('lightbg', 'light'):
356 colors='lightbg'
357 elif colors in ('dark', 'linux'):
358 colors='linux'
359 else:
360 colors='nocolor'
361 elif style:
362 if style=='bw':
363 colors='nocolor'
364 elif styles.dark_style(style):
365 colors='linux'
366 else:
367 colors='lightbg'
219 368 else:
220 kwargs['colors']=colors
221 if args.pylab:
222 kwargs['pylab']=args.pylab
223
224 kernel_manager.start_kernel(**kwargs)
225 kernel_manager.start_channels()
226
227 # Create the widget.
228 app = QtGui.QApplication([])
229 local_kernel = (not args.existing) or args.ip in LOCAL_IPS
230 if args.pure:
231 kind = 'plain' if args.plain else 'rich'
232 widget = FrontendWidget(kind=kind, paging=args.paging,
233 local_kernel=local_kernel)
234 elif args.plain:
235 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
236 else:
237 widget = RichIPythonWidget(paging=args.paging,
238 local_kernel=local_kernel)
239 widget.gui_completion = args.gui_completion
240 widget.kernel_manager = kernel_manager
241
242 # Configure the style.
243 if not args.pure: # only IPythonWidget supports styles
244 if args.style:
245 widget.syntax_style = args.style
246 widget.style_sheet = styles.sheet_from_template(args.style, colors)
369 colors=None
370
371 # Configure the style.
372 widget = self.widget
373 if style:
374 widget.style_sheet = styles.sheet_from_template(style, colors)
375 widget.syntax_style = style
247 376 widget._syntax_style_changed()
248 377 widget._style_sheet_changed()
249 378 elif colors:
@@ -254,23 +383,38 b' def main():'
254 383 # defaults to change
255 384 widget.set_default_style()
256 385
257 if args.stylesheet:
386 if self.stylesheet:
258 387 # we got an expicit stylesheet
259 if os.path.isfile(args.stylesheet):
260 with open(args.stylesheet) as f:
388 if os.path.isfile(self.stylesheet):
389 with open(self.stylesheet) as f:
261 390 sheet = f.read()
262 391 widget.style_sheet = sheet
263 392 widget._style_sheet_changed()
264 393 else:
265 raise IOError("Stylesheet %r not found."%args.stylesheet)
394 raise IOError("Stylesheet %r not found."%self.stylesheet)
395
396 def initialize(self, argv=None):
397 super(IPythonQtConsoleApp, self).initialize(argv)
398 self.init_kernel_manager()
399 self.init_qt_elements()
400 self.init_colors()
401
402 def start(self):
266 403
267 # Create the main window.
268 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
269 window.setWindowTitle('Python' if args.pure else 'IPython')
270 window.show()
404 # draw the window
405 self.window.show()
271 406
272 # Start the application main loop.
273 app.exec_()
407 # Start the application main loop.
408 self.app.exec_()
409
410 #-----------------------------------------------------------------------------
411 # Main entry point
412 #-----------------------------------------------------------------------------
413
414 def main():
415 app = IPythonQtConsoleApp()
416 app.initialize()
417 app.start()
274 418
275 419
276 420 if __name__ == '__main__':
@@ -7,7 +7,7 b' from IPython.external.qt import QtCore'
7 7 # IPython imports.
8 8 from IPython.utils.traitlets import Type
9 9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
10 ShellSocketChannel, StdInSocketChannel, HBSocketChannel
11 11 from util import MetaQObjectHasTraits, SuperQObject
12 12
13 13
@@ -20,7 +20,7 b' class SocketChannelQObject(SuperQObject):'
20 20 stopped = QtCore.Signal()
21 21
22 22 #---------------------------------------------------------------------------
23 # 'ZmqSocketChannel' interface
23 # 'ZMQSocketChannel' interface
24 24 #---------------------------------------------------------------------------
25 25
26 26 def start(self):
@@ -36,7 +36,7 b' class SocketChannelQObject(SuperQObject):'
36 36 self.stopped.emit()
37 37
38 38
39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
39 class QtShellSocketChannel(SocketChannelQObject, ShellSocketChannel):
40 40
41 41 # Emitted when any message is received.
42 42 message_received = QtCore.Signal(object)
@@ -56,7 +56,7 b' class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):'
56 56 _handlers_called = False
57 57
58 58 #---------------------------------------------------------------------------
59 # 'XReqSocketChannel' interface
59 # 'ShellSocketChannel' interface
60 60 #---------------------------------------------------------------------------
61 61
62 62 def call_handlers(self, msg):
@@ -76,7 +76,7 b' class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):'
76 76 self._handlers_called = True
77 77
78 78 #---------------------------------------------------------------------------
79 # 'QtXReqSocketChannel' interface
79 # 'QtShellSocketChannel' interface
80 80 #---------------------------------------------------------------------------
81 81
82 82 def reset_first_reply(self):
@@ -136,7 +136,7 b' class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):'
136 136 QtCore.QCoreApplication.instance().processEvents()
137 137
138 138
139 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
139 class QtStdInSocketChannel(SocketChannelQObject, StdInSocketChannel):
140 140
141 141 # Emitted when any message is received.
142 142 message_received = QtCore.Signal(object)
@@ -145,7 +145,7 b' class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):'
145 145 input_requested = QtCore.Signal(object)
146 146
147 147 #---------------------------------------------------------------------------
148 # 'RepSocketChannel' interface
148 # 'StdInSocketChannel' interface
149 149 #---------------------------------------------------------------------------
150 150
151 151 def call_handlers(self, msg):
@@ -190,8 +190,8 b' class QtKernelManager(KernelManager, SuperQObject):'
190 190
191 191 # Use Qt-specific channel classes that emit signals.
192 192 sub_channel_class = Type(QtSubSocketChannel)
193 xreq_channel_class = Type(QtXReqSocketChannel)
194 rep_channel_class = Type(QtRepSocketChannel)
193 shell_channel_class = Type(QtShellSocketChannel)
194 stdin_channel_class = Type(QtStdInSocketChannel)
195 195 hb_channel_class = Type(QtHBSocketChannel)
196 196
197 197 #---------------------------------------------------------------------------
@@ -203,8 +203,8 b' class QtKernelManager(KernelManager, SuperQObject):'
203 203 def start_kernel(self, *args, **kw):
204 204 """ Reimplemented for proper heartbeat management.
205 205 """
206 if self._xreq_channel is not None:
207 self._xreq_channel.reset_first_reply()
206 if self._shell_channel is not None:
207 self._shell_channel.reset_first_reply()
208 208 super(QtKernelManager, self).start_kernel(*args, **kw)
209 209
210 210 #------ Channel management -------------------------------------------------
@@ -222,13 +222,13 b' class QtKernelManager(KernelManager, SuperQObject):'
222 222 self.stopped_channels.emit()
223 223
224 224 @property
225 def xreq_channel(self):
225 def shell_channel(self):
226 226 """ Reimplemented for proper heartbeat management.
227 227 """
228 if self._xreq_channel is None:
229 self._xreq_channel = super(QtKernelManager, self).xreq_channel
230 self._xreq_channel.first_reply.connect(self._first_reply)
231 return self._xreq_channel
228 if self._shell_channel is None:
229 self._shell_channel = super(QtKernelManager, self).shell_channel
230 self._shell_channel.first_reply.connect(self._first_reply)
231 return self._shell_channel
232 232
233 233 #---------------------------------------------------------------------------
234 234 # Protected interface
@@ -58,11 +58,21 b' raw_input_original = raw_input'
58 58
59 59 class TerminalInteractiveShell(InteractiveShell):
60 60
61 autoedit_syntax = CBool(False, config=True)
61 autoedit_syntax = CBool(False, config=True,
62 help="auto editing of files with syntax errors.")
62 63 banner = Unicode('')
63 banner1 = Unicode(default_banner, config=True)
64 banner2 = Unicode('', config=True)
65 confirm_exit = CBool(True, config=True)
64 banner1 = Unicode(default_banner, config=True,
65 help="""The part of the banner to be printed before the profile"""
66 )
67 banner2 = Unicode('', config=True,
68 help="""The part of the banner to be printed after the profile"""
69 )
70 confirm_exit = CBool(True, config=True,
71 help="""
72 Set to confirm when you try to exit IPython with an EOF (Control-D
73 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
74 you can force a direct exit without any confirmation.""",
75 )
66 76 # This display_banner only controls whether or not self.show_banner()
67 77 # is called when mainloop/interact are called. The default is False
68 78 # because for the terminal based application, the banner behavior
@@ -71,19 +81,35 b' class TerminalInteractiveShell(InteractiveShell):'
71 81 display_banner = CBool(False) # This isn't configurable!
72 82 embedded = CBool(False)
73 83 embedded_active = CBool(False)
74 editor = Unicode(get_default_editor(), config=True)
75 pager = Unicode('less', config=True)
76
77 screen_length = Int(0, config=True)
78 term_title = CBool(False, config=True)
79
80 def __init__(self, config=None, ipython_dir=None, user_ns=None,
84 editor = Unicode(get_default_editor(), config=True,
85 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
86 )
87 pager = Unicode('less', config=True,
88 help="The shell program to be used for paging.")
89
90 screen_length = Int(0, config=True,
91 help=
92 """Number of lines of your screen, used to control printing of very
93 long strings. Strings longer than this number of lines will be sent
94 through a pager instead of directly printed. The default value for
95 this is 0, which means IPython will auto-detect your screen size every
96 time it needs to print certain potentially long strings (this doesn't
97 change the behavior of the 'print' keyword, it's only triggered
98 internally). If for some reason this isn't working well (it needs
99 curses support), specify it yourself. Otherwise don't change the
100 default.""",
101 )
102 term_title = CBool(False, config=True,
103 help="Enable auto setting the terminal title."
104 )
105
106 def __init__(self, config=None, ipython_dir=None, profile_dir=None, user_ns=None,
81 107 user_global_ns=None, custom_exceptions=((),None),
82 108 usage=None, banner1=None, banner2=None,
83 109 display_banner=None):
84 110
85 111 super(TerminalInteractiveShell, self).__init__(
86 config=config, ipython_dir=ipython_dir, user_ns=user_ns,
112 config=config, profile_dir=profile_dir, user_ns=user_ns,
87 113 user_global_ns=user_global_ns, custom_exceptions=custom_exceptions
88 114 )
89 115 # use os.system instead of utils.process.system by default, except on Windows
@@ -167,7 +193,7 b' class TerminalInteractiveShell(InteractiveShell):'
167 193
168 194 def compute_banner(self):
169 195 self.banner = self.banner1
170 if self.profile:
196 if self.profile and self.profile != 'default':
171 197 self.banner += '\nIPython profile: %s\n' % self.profile
172 198 if self.banner2:
173 199 self.banner += '\n' + self.banner2
This diff has been collapsed as it changes many lines, (695 lines changed) Show them Hide them
@@ -9,6 +9,7 b' Authors'
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 * Min Ragan-Kelley
12 13 """
13 14
14 15 #-----------------------------------------------------------------------------
@@ -28,17 +29,26 b' import logging'
28 29 import os
29 30 import sys
30 31
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader
34 )
35 from IPython.config.application import boolean_flag
31 36 from IPython.core import release
37 from IPython.core import usage
32 38 from IPython.core.crashhandler import CrashHandler
33 from IPython.core.application import Application, BaseAppConfigLoader
34 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
35 from IPython.config.loader import (
36 Config,
37 PyFileConfigLoader
39 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.application import (
41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 )
43 from IPython.core.shellapp import (
44 InteractiveShellApp, shell_flags, shell_aliases
38 45 )
46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
39 47 from IPython.lib import inputhook
40 from IPython.utils.path import filefind, get_ipython_dir, check_for_old_config
41 from IPython.core import usage
48 from IPython.utils.path import get_ipython_dir, check_for_old_config
49 from IPython.utils.traitlets import (
50 Bool, Dict, CaselessStrEnum
51 )
42 52
43 53 #-----------------------------------------------------------------------------
44 54 # Globals, utilities and helpers
@@ -48,276 +58,6 b' from IPython.core import usage'
48 58 default_config_file_name = u'ipython_config.py'
49 59
50 60
51 class IPAppConfigLoader(BaseAppConfigLoader):
52
53 def _add_arguments(self):
54 super(IPAppConfigLoader, self)._add_arguments()
55 paa = self.parser.add_argument
56 paa('-p',
57 '--profile', dest='Global.profile', type=unicode,
58 help=
59 """The string name of the ipython profile to be used. Assume that your
60 config file is ipython_config-<name>.py (looks in current dir first,
61 then in IPYTHON_DIR). This is a quick way to keep and load multiple
62 config files for different tasks, especially if include your basic one
63 in your more specialized ones. You can keep a basic
64 IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
65 include this one and load extra things for particular tasks.""",
66 metavar='Global.profile')
67 paa('--config-file',
68 dest='Global.config_file', type=unicode,
69 help=
70 """Set the config file name to override default. Normally IPython
71 loads ipython_config.py (from current directory) or
72 IPYTHON_DIR/ipython_config.py. If the loading of your config file
73 fails, IPython starts with a bare bones configuration (no modules
74 loaded at all).""",
75 metavar='Global.config_file')
76 paa('--autocall',
77 dest='InteractiveShell.autocall', type=int,
78 help=
79 """Make IPython automatically call any callable object even if you
80 didn't type explicit parentheses. For example, 'str 43' becomes
81 'str(43)' automatically. The value can be '0' to disable the feature,
82 '1' for 'smart' autocall, where it is not applied if there are no more
83 arguments on the line, and '2' for 'full' autocall, where all callable
84 objects are automatically called (even if no arguments are present).
85 The default is '1'.""",
86 metavar='InteractiveShell.autocall')
87 paa('--autoindent',
88 action='store_true', dest='InteractiveShell.autoindent',
89 help='Turn on autoindenting.')
90 paa('--no-autoindent',
91 action='store_false', dest='InteractiveShell.autoindent',
92 help='Turn off autoindenting.')
93 paa('--automagic',
94 action='store_true', dest='InteractiveShell.automagic',
95 help=
96 """Turn on the auto calling of magic commands. Type %%magic at the
97 IPython prompt for more information.""")
98 paa('--no-automagic',
99 action='store_false', dest='InteractiveShell.automagic',
100 help='Turn off the auto calling of magic commands.')
101 paa('--autoedit-syntax',
102 action='store_true', dest='TerminalInteractiveShell.autoedit_syntax',
103 help='Turn on auto editing of files with syntax errors.')
104 paa('--no-autoedit-syntax',
105 action='store_false', dest='TerminalInteractiveShell.autoedit_syntax',
106 help='Turn off auto editing of files with syntax errors.')
107 paa('--banner',
108 action='store_true', dest='Global.display_banner',
109 help='Display a banner upon starting IPython.')
110 paa('--no-banner',
111 action='store_false', dest='Global.display_banner',
112 help="Don't display a banner upon starting IPython.")
113 paa('--cache-size',
114 type=int, dest='InteractiveShell.cache_size',
115 help=
116 """Set the size of the output cache. The default is 1000, you can
117 change it permanently in your config file. Setting it to 0 completely
118 disables the caching system, and the minimum value accepted is 20 (if
119 you provide a value less than 20, it is reset to 0 and a warning is
120 issued). This limit is defined because otherwise you'll spend more
121 time re-flushing a too small cache than working""",
122 metavar='InteractiveShell.cache_size')
123 paa('--classic',
124 action='store_true', dest='Global.classic',
125 help="Gives IPython a similar feel to the classic Python prompt.")
126 paa('--colors',
127 type=str, dest='InteractiveShell.colors',
128 help="Set the color scheme (NoColor, Linux, and LightBG).",
129 metavar='InteractiveShell.colors')
130 paa('--color-info',
131 action='store_true', dest='InteractiveShell.color_info',
132 help=
133 """IPython can display information about objects via a set of func-
134 tions, and optionally can use colors for this, syntax highlighting
135 source code and various other elements. However, because this
136 information is passed through a pager (like 'less') and many pagers get
137 confused with color codes, this option is off by default. You can test
138 it and turn it on permanently in your ipython_config.py file if it
139 works for you. Test it and turn it on permanently if it works with
140 your system. The magic function %%color_info allows you to toggle this
141 inter- actively for testing.""")
142 paa('--no-color-info',
143 action='store_false', dest='InteractiveShell.color_info',
144 help="Disable using colors for info related things.")
145 paa('--confirm-exit',
146 action='store_true', dest='TerminalInteractiveShell.confirm_exit',
147 help=
148 """Set to confirm when you try to exit IPython with an EOF (Control-D
149 in Unix, Control-Z/Enter in Windows). By typing 'exit', 'quit' or
150 '%%Exit', you can force a direct exit without any confirmation.""")
151 paa('--no-confirm-exit',
152 action='store_false', dest='TerminalInteractiveShell.confirm_exit',
153 help="Don't prompt the user when exiting.")
154 paa('--deep-reload',
155 action='store_true', dest='InteractiveShell.deep_reload',
156 help=
157 """Enable deep (recursive) reloading by default. IPython can use the
158 deep_reload module which reloads changes in modules recursively (it
159 replaces the reload() function, so you don't need to change anything to
160 use it). deep_reload() forces a full reload of modules whose code may
161 have changed, which the default reload() function does not. When
162 deep_reload is off, IPython will use the normal reload(), but
163 deep_reload will still be available as dreload(). This fea- ture is off
164 by default [which means that you have both normal reload() and
165 dreload()].""")
166 paa('--no-deep-reload',
167 action='store_false', dest='InteractiveShell.deep_reload',
168 help="Disable deep (recursive) reloading by default.")
169 paa('--editor',
170 type=str, dest='TerminalInteractiveShell.editor',
171 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
172 metavar='TerminalInteractiveShell.editor')
173 paa('--log','-l',
174 action='store_true', dest='InteractiveShell.logstart',
175 help="Start logging to the default log file (./ipython_log.py).")
176 paa('--logfile','-lf',
177 type=unicode, dest='InteractiveShell.logfile',
178 help="Start logging to logfile with this name.",
179 metavar='InteractiveShell.logfile')
180 paa('--log-append','-la',
181 type=unicode, dest='InteractiveShell.logappend',
182 help="Start logging to the given file in append mode.",
183 metavar='InteractiveShell.logfile')
184 paa('--pdb',
185 action='store_true', dest='InteractiveShell.pdb',
186 help="Enable auto calling the pdb debugger after every exception.")
187 paa('--no-pdb',
188 action='store_false', dest='InteractiveShell.pdb',
189 help="Disable auto calling the pdb debugger after every exception.")
190 paa('--pprint',
191 action='store_true', dest='PlainTextFormatter.pprint',
192 help="Enable auto pretty printing of results.")
193 paa('--no-pprint',
194 action='store_false', dest='PlainTextFormatter.pprint',
195 help="Disable auto auto pretty printing of results.")
196 paa('--prompt-in1','-pi1',
197 type=str, dest='InteractiveShell.prompt_in1',
198 help=
199 """Set the main input prompt ('In [\#]: '). Note that if you are using
200 numbered prompts, the number is represented with a '\#' in the string.
201 Don't forget to quote strings with spaces embedded in them. Most
202 bash-like escapes can be used to customize IPython's prompts, as well
203 as a few additional ones which are IPython-spe- cific. All valid
204 prompt escapes are described in detail in the Customization section of
205 the IPython manual.""",
206 metavar='InteractiveShell.prompt_in1')
207 paa('--prompt-in2','-pi2',
208 type=str, dest='InteractiveShell.prompt_in2',
209 help=
210 """Set the secondary input prompt (' .\D.: '). Similar to the previous
211 option, but used for the continuation prompts. The special sequence
212 '\D' is similar to '\#', but with all digits replaced by dots (so you
213 can have your continuation prompt aligned with your input prompt).
214 Default: ' .\D.: ' (note three spaces at the start for alignment with
215 'In [\#]')""",
216 metavar='InteractiveShell.prompt_in2')
217 paa('--prompt-out','-po',
218 type=str, dest='InteractiveShell.prompt_out',
219 help="Set the output prompt ('Out[\#]:')",
220 metavar='InteractiveShell.prompt_out')
221 paa('--quick',
222 action='store_true', dest='Global.quick',
223 help="Enable quick startup with no config files.")
224 paa('--readline',
225 action='store_true', dest='InteractiveShell.readline_use',
226 help="Enable readline for command line usage.")
227 paa('--no-readline',
228 action='store_false', dest='InteractiveShell.readline_use',
229 help="Disable readline for command line usage.")
230 paa('--screen-length','-sl',
231 type=int, dest='TerminalInteractiveShell.screen_length',
232 help=
233 """Number of lines of your screen, used to control printing of very
234 long strings. Strings longer than this number of lines will be sent
235 through a pager instead of directly printed. The default value for
236 this is 0, which means IPython will auto-detect your screen size every
237 time it needs to print certain potentially long strings (this doesn't
238 change the behavior of the 'print' keyword, it's only triggered
239 internally). If for some reason this isn't working well (it needs
240 curses support), specify it yourself. Otherwise don't change the
241 default.""",
242 metavar='TerminalInteractiveShell.screen_length')
243 paa('--separate-in','-si',
244 type=str, dest='InteractiveShell.separate_in',
245 help="Separator before input prompts. Default '\\n'.",
246 metavar='InteractiveShell.separate_in')
247 paa('--separate-out','-so',
248 type=str, dest='InteractiveShell.separate_out',
249 help="Separator before output prompts. Default 0 (nothing).",
250 metavar='InteractiveShell.separate_out')
251 paa('--separate-out2','-so2',
252 type=str, dest='InteractiveShell.separate_out2',
253 help="Separator after output prompts. Default 0 (nonight).",
254 metavar='InteractiveShell.separate_out2')
255 paa('--no-sep',
256 action='store_true', dest='Global.nosep',
257 help="Eliminate all spacing between prompts.")
258 paa('--term-title',
259 action='store_true', dest='TerminalInteractiveShell.term_title',
260 help="Enable auto setting the terminal title.")
261 paa('--no-term-title',
262 action='store_false', dest='TerminalInteractiveShell.term_title',
263 help="Disable auto setting the terminal title.")
264 paa('--xmode',
265 type=str, dest='InteractiveShell.xmode',
266 help=
267 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
268 similar to python's normal traceback printing. Context: prints 5 lines
269 of context source code around each line in the traceback. Verbose:
270 similar to Context, but additionally prints the variables currently
271 visible where the exception happened (shortening their strings if too
272 long). This can potentially be very slow, if you happen to have a huge
273 data structure whose string representation is complex to compute.
274 Your computer may appear to freeze for a while with cpu usage at 100%%.
275 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
276 it more than once).
277 """,
278 metavar='InteractiveShell.xmode')
279 paa('--ext',
280 type=str, dest='Global.extra_extension',
281 help="The dotted module name of an IPython extension to load.",
282 metavar='Global.extra_extension')
283 paa('-c',
284 type=str, dest='Global.code_to_run',
285 help="Execute the given command string.",
286 metavar='Global.code_to_run')
287 paa('-i',
288 action='store_true', dest='Global.force_interact',
289 help=
290 "If running code from the command line, become interactive afterwards.")
291
292 # Options to start with GUI control enabled from the beginning
293 paa('--gui',
294 type=str, dest='Global.gui',
295 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
296 metavar='gui-mode')
297 paa('--pylab','-pylab',
298 type=str, dest='Global.pylab',
299 nargs='?', const='auto', metavar='gui-mode',
300 help="Pre-load matplotlib and numpy for interactive use. "+
301 "If no value is given, the gui backend is matplotlib's, else use "+
302 "one of: ['tk', 'qt', 'wx', 'gtk', 'osx'].")
303
304 # Legacy GUI options. Leave them in for backwards compatibility, but the
305 # 'thread' names are really a misnomer now.
306 paa('--wthread', '-wthread',
307 action='store_true', dest='Global.wthread',
308 help=
309 """Enable wxPython event loop integration. (DEPRECATED, use --gui wx)""")
310 paa('--q4thread', '--qthread', '-q4thread', '-qthread',
311 action='store_true', dest='Global.q4thread',
312 help=
313 """Enable Qt4 event loop integration. Qt3 is no longer supported.
314 (DEPRECATED, use --gui qt)""")
315 paa('--gthread', '-gthread',
316 action='store_true', dest='Global.gthread',
317 help=
318 """Enable GTK event loop integration. (DEPRECATED, use --gui gtk)""")
319
320
321 61 #-----------------------------------------------------------------------------
322 62 # Crash handler for this application
323 63 #-----------------------------------------------------------------------------
@@ -377,271 +117,219 b' class IPAppCrashHandler(CrashHandler):'
377 117
378 118 return ''.join(report)
379 119
120 #-----------------------------------------------------------------------------
121 # Aliases and Flags
122 #-----------------------------------------------------------------------------
123 flags = dict(base_flags)
124 flags.update(shell_flags)
125 addflag = lambda *args: flags.update(boolean_flag(*args))
126 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 'Turn on auto editing of files with syntax errors.',
128 'Turn off auto editing of files with syntax errors.'
129 )
130 addflag('banner', 'TerminalIPythonApp.display_banner',
131 "Display a banner upon starting IPython.",
132 "Don't display a banner upon starting IPython."
133 )
134 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 you can force a direct exit without any confirmation.""",
138 "Don't prompt the user when exiting."
139 )
140 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 "Enable auto setting the terminal title.",
142 "Disable auto setting the terminal title."
143 )
144 classic_config = Config()
145 classic_config.InteractiveShell.cache_size = 0
146 classic_config.PlainTextFormatter.pprint = False
147 classic_config.InteractiveShell.prompt_in1 = '>>> '
148 classic_config.InteractiveShell.prompt_in2 = '... '
149 classic_config.InteractiveShell.prompt_out = ''
150 classic_config.InteractiveShell.separate_in = ''
151 classic_config.InteractiveShell.separate_out = ''
152 classic_config.InteractiveShell.separate_out2 = ''
153 classic_config.InteractiveShell.colors = 'NoColor'
154 classic_config.InteractiveShell.xmode = 'Plain'
155
156 flags['classic']=(
157 classic_config,
158 "Gives IPython a similar feel to the classic Python prompt."
159 )
160 # # log doesn't make so much sense this way anymore
161 # paa('--log','-l',
162 # action='store_true', dest='InteractiveShell.logstart',
163 # help="Start logging to the default log file (./ipython_log.py).")
164 #
165 # # quick is harder to implement
166 flags['quick']=(
167 {'TerminalIPythonApp' : {'quick' : True}},
168 "Enable quick startup with no config files."
169 )
170
171 flags['i'] = (
172 {'TerminalIPythonApp' : {'force_interact' : True}},
173 "If running code from the command line, become interactive afterwards."
174 )
175 flags['pylab'] = (
176 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
177 """Pre-load matplotlib and numpy for interactive use with
178 the default matplotlib backend."""
179 )
180
181 aliases = dict(base_aliases)
182 aliases.update(shell_aliases)
183
184 # it's possible we don't want short aliases for *all* of these:
185 aliases.update(dict(
186 gui='TerminalIPythonApp.gui',
187 pylab='TerminalIPythonApp.pylab',
188 ))
380 189
381 190 #-----------------------------------------------------------------------------
382 191 # Main classes and functions
383 192 #-----------------------------------------------------------------------------
384 193
385 class IPythonApp(Application):
194 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
386 195 name = u'ipython'
387 #: argparse formats better the 'usage' than the 'description' field
388 description = None
389 usage = usage.cl_usage
390 command_line_loader = IPAppConfigLoader
196 description = usage.cl_usage
391 197 default_config_file_name = default_config_file_name
392 198 crash_handler_class = IPAppCrashHandler
199
200 flags = Dict(flags)
201 aliases = Dict(aliases)
202 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
203 subcommands = Dict(dict(
204 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
205 """Launch the IPython Qt Console."""
206 ),
207 profile = ("IPython.core.profileapp.ProfileApp",
208 "Create and manage IPython profiles.")
209 ))
210
211 # *do* autocreate requested profile, but don't create the config file.
212 auto_create=Bool(True)
213 # configurables
214 ignore_old_config=Bool(False, config=True,
215 help="Suppress warning messages about legacy config files"
216 )
217 quick = Bool(False, config=True,
218 help="""Start IPython quickly by skipping the loading of config files."""
219 )
220 def _quick_changed(self, name, old, new):
221 if new:
222 self.load_config_file = lambda *a, **kw: None
223 self.ignore_old_config=True
224
225 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
226 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
227 )
228 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
229 config=True,
230 help="""Pre-load matplotlib and numpy for interactive use,
231 selecting a particular matplotlib backend and loop integration.
232 """
233 )
234 display_banner = Bool(True, config=True,
235 help="Whether to display a banner upon starting IPython."
236 )
237
238 # if there is code of files to run from the cmd line, don't interact
239 # unless the --i flag (App.force_interact) is true.
240 force_interact = Bool(False, config=True,
241 help="""If a command or file is given via the command-line,
242 e.g. 'ipython foo.py"""
243 )
244 def _force_interact_changed(self, name, old, new):
245 if new:
246 self.interact = True
247
248 def _file_to_run_changed(self, name, old, new):
249 if new and not self.force_interact:
250 self.interact = False
251 _code_to_run_changed = _file_to_run_changed
252
253 # internal, not-configurable
254 interact=Bool(True)
255
256
257 def initialize(self, argv=None):
258 """Do actions after construct, but before starting the app."""
259 super(TerminalIPythonApp, self).initialize(argv)
260 if self.subapp is not None:
261 # don't bother initializing further, starting subapp
262 return
263 if not self.ignore_old_config:
264 check_for_old_config(self.ipython_dir)
265 # print self.extra_args
266 if self.extra_args:
267 self.file_to_run = self.extra_args[0]
268 # create the shell
269 self.init_shell()
270 # and draw the banner
271 self.init_banner()
272 # Now a variety of things that happen after the banner is printed.
273 self.init_gui_pylab()
274 self.init_extensions()
275 self.init_code()
393 276
394 def create_default_config(self):
395 super(IPythonApp, self).create_default_config()
396 # Eliminate multiple lookups
397 Global = self.default_config.Global
398
399 # Set all default values
400 Global.display_banner = True
401
402 # If the -c flag is given or a file is given to run at the cmd line
403 # like "ipython foo.py", normally we exit without starting the main
404 # loop. The force_interact config variable allows a user to override
405 # this and interact. It is also set by the -i cmd line flag, just
406 # like Python.
407 Global.force_interact = False
408
409 # By default always interact by starting the IPython mainloop.
410 Global.interact = True
411
412 # No GUI integration by default
413 Global.gui = False
414 # Pylab off by default
415 Global.pylab = False
416
417 # Deprecated versions of gui support that used threading, we support
418 # them just for bacwards compatibility as an alternate spelling for
419 # '--gui X'
420 Global.qthread = False
421 Global.q4thread = False
422 Global.wthread = False
423 Global.gthread = False
424
425 def load_file_config(self):
426 if hasattr(self.command_line_config.Global, 'quick'):
427 if self.command_line_config.Global.quick:
428 self.file_config = Config()
429 return
430 super(IPythonApp, self).load_file_config()
431
432 def post_load_file_config(self):
433 if hasattr(self.command_line_config.Global, 'extra_extension'):
434 if not hasattr(self.file_config.Global, 'extensions'):
435 self.file_config.Global.extensions = []
436 self.file_config.Global.extensions.append(
437 self.command_line_config.Global.extra_extension)
438 del self.command_line_config.Global.extra_extension
439
440 def pre_construct(self):
441 config = self.master_config
442
443 if hasattr(config.Global, 'classic'):
444 if config.Global.classic:
445 config.InteractiveShell.cache_size = 0
446 config.PlainTextFormatter.pprint = False
447 config.InteractiveShell.prompt_in1 = '>>> '
448 config.InteractiveShell.prompt_in2 = '... '
449 config.InteractiveShell.prompt_out = ''
450 config.InteractiveShell.separate_in = \
451 config.InteractiveShell.separate_out = \
452 config.InteractiveShell.separate_out2 = ''
453 config.InteractiveShell.colors = 'NoColor'
454 config.InteractiveShell.xmode = 'Plain'
455
456 if hasattr(config.Global, 'nosep'):
457 if config.Global.nosep:
458 config.InteractiveShell.separate_in = \
459 config.InteractiveShell.separate_out = \
460 config.InteractiveShell.separate_out2 = ''
461
462 # if there is code of files to run from the cmd line, don't interact
463 # unless the -i flag (Global.force_interact) is true.
464 code_to_run = config.Global.get('code_to_run','')
465 file_to_run = False
466 if self.extra_args and self.extra_args[0]:
467 file_to_run = True
468 if file_to_run or code_to_run:
469 if not config.Global.force_interact:
470 config.Global.interact = False
471
472 def construct(self):
277 def init_shell(self):
278 """initialize the InteractiveShell instance"""
473 279 # I am a little hesitant to put these into InteractiveShell itself.
474 280 # But that might be the place for them
475 281 sys.path.insert(0, '')
476 282
477 283 # Create an InteractiveShell instance.
478 self.shell = TerminalInteractiveShell.instance(config=self.master_config)
479
480 def post_construct(self):
481 """Do actions after construct, but before starting the app."""
482 config = self.master_config
483
484 284 # shell.display_banner should always be False for the terminal
485 285 # based app, because we call shell.show_banner() by hand below
486 286 # so the banner shows *before* all extension loading stuff.
487 self.shell.display_banner = False
488 if config.Global.display_banner and \
489 config.Global.interact:
490 self.shell.show_banner()
287 self.shell = TerminalInteractiveShell.instance(config=self.config,
288 display_banner=False, profile_dir=self.profile_dir,
289 ipython_dir=self.ipython_dir)
491 290
291 def init_banner(self):
292 """optionally display the banner"""
293 if self.display_banner and self.interact:
294 self.shell.show_banner()
492 295 # Make sure there is a space below the banner.
493 296 if self.log_level <= logging.INFO: print
494 297
495 # Now a variety of things that happen after the banner is printed.
496 self._enable_gui_pylab()
497 self._load_extensions()
498 self._run_exec_lines()
499 self._run_exec_files()
500 self._run_cmd_line_code()
501 298
502 def _enable_gui_pylab(self):
299 def init_gui_pylab(self):
503 300 """Enable GUI event loop integration, taking pylab into account."""
504 Global = self.master_config.Global
505
506 # Select which gui to use
507 if Global.gui:
508 gui = Global.gui
509 # The following are deprecated, but there's likely to be a lot of use
510 # of this form out there, so we might as well support it for now. But
511 # the --gui option above takes precedence.
512 elif Global.wthread:
513 gui = inputhook.GUI_WX
514 elif Global.qthread:
515 gui = inputhook.GUI_QT
516 elif Global.gthread:
517 gui = inputhook.GUI_GTK
518 else:
519 gui = None
301 gui = self.gui
520 302
521 # Using --pylab will also require gui activation, though which toolkit
303 # Using `pylab` will also require gui activation, though which toolkit
522 304 # to use may be chosen automatically based on mpl configuration.
523 if Global.pylab:
305 if self.pylab:
524 306 activate = self.shell.enable_pylab
525 if Global.pylab == 'auto':
307 if self.pylab == 'auto':
526 308 gui = None
527 309 else:
528 gui = Global.pylab
310 gui = self.pylab
529 311 else:
530 312 # Enable only GUI integration, no pylab
531 313 activate = inputhook.enable_gui
532 314
533 if gui or Global.pylab:
315 if gui or self.pylab:
534 316 try:
535 317 self.log.info("Enabling GUI event loop integration, "
536 "toolkit=%s, pylab=%s" % (gui, Global.pylab) )
318 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
537 319 activate(gui)
538 320 except:
539 321 self.log.warn("Error in enabling GUI event loop integration:")
540 322 self.shell.showtraceback()
541 323
542 def _load_extensions(self):
543 """Load all IPython extensions in Global.extensions.
544
545 This uses the :meth:`ExtensionManager.load_extensions` to load all
546 the extensions listed in ``self.master_config.Global.extensions``.
547 """
548 try:
549 if hasattr(self.master_config.Global, 'extensions'):
550 self.log.debug("Loading IPython extensions...")
551 extensions = self.master_config.Global.extensions
552 for ext in extensions:
553 try:
554 self.log.info("Loading IPython extension: %s" % ext)
555 self.shell.extension_manager.load_extension(ext)
556 except:
557 self.log.warn("Error in loading extension: %s" % ext)
558 self.shell.showtraceback()
559 except:
560 self.log.warn("Unknown error in loading extensions:")
561 self.shell.showtraceback()
562
563 def _run_exec_lines(self):
564 """Run lines of code in Global.exec_lines in the user's namespace."""
565 try:
566 if hasattr(self.master_config.Global, 'exec_lines'):
567 self.log.debug("Running code from Global.exec_lines...")
568 exec_lines = self.master_config.Global.exec_lines
569 for line in exec_lines:
570 try:
571 self.log.info("Running code in user namespace: %s" %
572 line)
573 self.shell.run_cell(line, store_history=False)
574 except:
575 self.log.warn("Error in executing line in user "
576 "namespace: %s" % line)
577 self.shell.showtraceback()
578 except:
579 self.log.warn("Unknown error in handling Global.exec_lines:")
580 self.shell.showtraceback()
581
582 def _exec_file(self, fname):
583 full_filename = filefind(fname, [u'.', self.ipython_dir])
584 if os.path.isfile(full_filename):
585 if full_filename.endswith(u'.py'):
586 self.log.info("Running file in user namespace: %s" %
587 full_filename)
588 # Ensure that __file__ is always defined to match Python behavior
589 self.shell.user_ns['__file__'] = fname
590 try:
591 self.shell.safe_execfile(full_filename, self.shell.user_ns)
592 finally:
593 del self.shell.user_ns['__file__']
594 elif full_filename.endswith('.ipy'):
595 self.log.info("Running file in user namespace: %s" %
596 full_filename)
597 self.shell.safe_execfile_ipy(full_filename)
598 else:
599 self.log.warn("File does not have a .py or .ipy extension: <%s>"
600 % full_filename)
601 def _run_exec_files(self):
602 try:
603 if hasattr(self.master_config.Global, 'exec_files'):
604 self.log.debug("Running files in Global.exec_files...")
605 exec_files = self.master_config.Global.exec_files
606 for fname in exec_files:
607 self._exec_file(fname)
608 except:
609 self.log.warn("Unknown error in handling Global.exec_files:")
610 self.shell.showtraceback()
611
612 def _run_cmd_line_code(self):
613 if hasattr(self.master_config.Global, 'code_to_run'):
614 line = self.master_config.Global.code_to_run
615 try:
616 self.log.info("Running code given at command line (-c): %s" %
617 line)
618 self.shell.run_cell(line, store_history=False)
619 except:
620 self.log.warn("Error in executing line in user namespace: %s" %
621 line)
622 self.shell.showtraceback()
623 return
624 # Like Python itself, ignore the second if the first of these is present
625 try:
626 fname = self.extra_args[0]
627 except:
628 pass
629 else:
630 try:
631 self._exec_file(fname)
632 except:
633 self.log.warn("Error in executing file in user namespace: %s" %
634 fname)
635 self.shell.showtraceback()
636
637 def start_app(self):
638 if not getattr(self.master_config.Global, 'ignore_old_config', False):
639 check_for_old_config(self.ipython_dir)
640 if self.master_config.Global.interact:
324 def start(self):
325 if self.subapp is not None:
326 return self.subapp.start()
327 # perform any prexec steps:
328 if self.interact:
641 329 self.log.debug("Starting IPython's mainloop...")
642 330 self.shell.mainloop()
643 331 else:
644 self.log.debug("IPython not interactive, start_app is no-op...")
332 self.log.debug("IPython not interactive...")
645 333
646 334
647 335 def load_default_config(ipython_dir=None):
@@ -651,16 +339,19 b' def load_default_config(ipython_dir=None):'
651 339 """
652 340 if ipython_dir is None:
653 341 ipython_dir = get_ipython_dir()
654 cl = PyFileConfigLoader(default_config_file_name, ipython_dir)
342 profile_dir = os.path.join(ipython_dir, 'profile_default')
343 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
655 344 config = cl.load_config()
656 345 return config
657 346
658 347
659 348 def launch_new_instance():
660 349 """Create and run a full blown IPython instance"""
661 app = IPythonApp()
350 app = TerminalIPythonApp.instance()
351 app.initialize()
662 352 app.start()
663 353
664 354
665 355 if __name__ == '__main__':
666 356 launch_new_instance()
357
@@ -304,9 +304,7 b' class IPythonRunner(InteractiveRunner):'
304 304 def __init__(self,program = 'ipython',args=None,out=sys.stdout,echo=True):
305 305 """New runner, optionally passing the ipython command to use."""
306 306
307 args0 = ['--colors','NoColor',
308 '-pi1','In [\\#]: ',
309 '-pi2',' .\\D.: ',
307 args0 = ['colors=NoColor',
310 308 '--no-term-title',
311 309 '--no-autoindent']
312 310 if args is None: args = args0
@@ -87,8 +87,8 b' def figsize(sizex, sizey):'
87 87 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
88 88
89 89
90 def figure_to_svg(fig):
91 """Convert a figure to svg for inline display."""
90 def print_figure(fig, fmt='png'):
91 """Convert a figure to svg or png for inline display."""
92 92 # When there's an empty figure, we shouldn't return anything, otherwise we
93 93 # get big blank areas in the qt console.
94 94 if not fig.axes:
@@ -100,12 +100,13 b' def figure_to_svg(fig):'
100 100 fig.set_edgecolor('white')
101 101 try:
102 102 string_io = StringIO()
103 fig.canvas.print_figure(string_io, format='svg')
104 svg = string_io.getvalue()
103 # use 72 dpi to match QTConsole's dpi
104 fig.canvas.print_figure(string_io, format=fmt, dpi=72)
105 data = string_io.getvalue()
105 106 finally:
106 107 fig.set_facecolor(fc)
107 108 fig.set_edgecolor(ec)
108 return svg
109 return data
109 110
110 111
111 112 # We need a little factory function here to create the closure where
@@ -150,6 +151,29 b' def mpl_runner(safe_execfile):'
150 151 return mpl_execfile
151 152
152 153
154 def select_figure_format(shell, fmt):
155 """Select figure format for inline backend, either 'png' or 'svg'.
156
157 Using this method ensures only one figure format is active at a time.
158 """
159 from matplotlib.figure import Figure
160 from IPython.zmq.pylab import backend_inline
161
162 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
163 png_formatter = shell.display_formatter.formatters['image/png']
164
165 if fmt=='png':
166 svg_formatter.type_printers.pop(Figure, None)
167 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
168 elif fmt=='svg':
169 png_formatter.type_printers.pop(Figure, None)
170 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
171 else:
172 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
173
174 # set the format to be used in the backend()
175 backend_inline._figure_format = fmt
176
153 177 #-----------------------------------------------------------------------------
154 178 # Code for initializing matplotlib and importing pylab
155 179 #-----------------------------------------------------------------------------
@@ -208,7 +232,6 b' def activate_matplotlib(backend):'
208 232 # For this, we wrap it into a decorator which adds a 'called' flag.
209 233 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
210 234
211
212 235 def import_pylab(user_ns, backend, import_all=True, shell=None):
213 236 """Import the standard pylab symbols into user_ns."""
214 237
@@ -228,43 +251,32 b' def import_pylab(user_ns, backend, import_all=True, shell=None):'
228 251 # If using our svg payload backend, register the post-execution
229 252 # function that will pick up the results for display. This can only be
230 253 # done with access to the real shell object.
254 #
255 from IPython.zmq.pylab.backend_inline import InlineBackendConfig
256
257 cfg = InlineBackendConfig.instance(config=shell.config)
258 cfg.shell = shell
259
231 260 if backend == backends['inline']:
232 from IPython.zmq.pylab.backend_inline import flush_svg
261 from IPython.zmq.pylab.backend_inline import flush_figures
233 262 from matplotlib import pyplot
234 shell.register_post_execute(flush_svg)
235 # The typical default figure size is too large for inline use,
236 # so we shrink the figure size to 6x4, and tweak fonts to
237 # make that fit. This is configurable via Global.pylab_inline_rc,
238 # or rather it will be once the zmq kernel is hooked up to
239 # the config system.
240
241 default_rc = {
242 'figure.figsize': (6.0,4.0),
243 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
244 'font.size': 10,
245 # 10pt still needs a little more room on the xlabel:
246 'figure.subplot.bottom' : .125
247 }
248 rc = getattr(shell.config.Global, 'pylab_inline_rc', default_rc)
249 pyplot.rcParams.update(rc)
250 shell.config.Global.pylab_inline_rc = rc
263 shell.register_post_execute(flush_figures)
264 # load inline_rc
265 pyplot.rcParams.update(cfg.rc)
251 266
252 267 # Add 'figsize' to pyplot and to the user's namespace
253 268 user_ns['figsize'] = pyplot.figsize = figsize
254 269 shell.user_ns_hidden['figsize'] = figsize
255 270
271 # Setup the default figure format
272 fmt = cfg.figure_format
273 select_figure_format(shell, fmt)
274
256 275 # The old pastefig function has been replaced by display
257 # Always add this svg formatter so display works.
258 from IPython.core.display import display, display_svg
259 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
260 svg_formatter.for_type_by_name(
261 'matplotlib.figure','Figure',figure_to_svg
262 )
276 from IPython.core.display import display
263 277 # Add display and display_png to the user's namespace
264 278 user_ns['display'] = display
265 279 shell.user_ns_hidden['display'] = display
266 user_ns['display_svg'] = display_svg
267 shell.user_ns_hidden['display_svg'] = display_svg
268 280 user_ns['getfigs'] = getfigs
269 281 shell.user_ns_hidden['getfigs'] = getfigs
270 282
@@ -41,7 +41,7 b' from .. import pylabtools as pt'
41 41 def test_figure_to_svg():
42 42 # simple empty-figure test
43 43 fig = plt.figure()
44 yield nt.assert_equal(pt.figure_to_svg(fig), None)
44 yield nt.assert_equal(pt.print_figure(fig, 'svg'), None)
45 45
46 46 plt.close('all')
47 47
@@ -50,5 +50,5 b' def test_figure_to_svg():'
50 50 ax = fig.add_subplot(1,1,1)
51 51 ax.plot([1,2,3])
52 52 plt.draw()
53 svg = pt.figure_to_svg(fig)[:100].lower()
53 svg = pt.print_figure(fig, 'svg')[:100].lower()
54 54 yield nt.assert_true('doctype svg' in svg)
@@ -1,4 +1,9 b''
1 """The IPython ZMQ-based parallel computing interface."""
1 """The IPython ZMQ-based parallel computing interface.
2
3 Authors:
4
5 * MinRK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2011 The IPython Development Team
4 9 #
This diff has been collapsed as it changes many lines, (753 lines changed) Show them Hide them
@@ -2,10 +2,16 b''
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
5 11 """
6 12
7 13 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
9 15 #
10 16 # Distributed under the terms of the BSD License. The full license is in
11 17 # the file COPYING, distributed as part of this software.
@@ -25,12 +31,18 b' from subprocess import check_call, CalledProcessError, PIPE'
25 31 import zmq
26 32 from zmq.eventloop import ioloop
27 33
28 from IPython.external.argparse import ArgumentParser, SUPPRESS
34 from IPython.config.application import Application, boolean_flag
35 from IPython.config.loader import Config
36 from IPython.core.application import BaseIPythonApplication
37 from IPython.core.profiledir import ProfileDir
38 from IPython.utils.daemonize import daemonize
29 39 from IPython.utils.importstring import import_item
40 from IPython.utils.traitlets import Int, Unicode, Bool, CFloat, Dict, List
30 41
31 from IPython.parallel.apps.clusterdir import (
32 ApplicationWithClusterDir, ClusterDirConfigLoader,
33 ClusterDirError, PIDFileError
42 from IPython.parallel.apps.baseapp import (
43 BaseParallelApplication,
44 PIDFileError,
45 base_flags, base_aliases
34 46 )
35 47
36 48
@@ -42,16 +54,15 b' from IPython.parallel.apps.clusterdir import ('
42 54 default_config_file_name = u'ipcluster_config.py'
43 55
44 56
45 _description = """\
46 Start an IPython cluster for parallel computing.\n\n
57 _description = """Start an IPython cluster for parallel computing.
47 58
48 59 An IPython cluster consists of 1 controller and 1 or more engines.
49 60 This command automates the startup of these processes using a wide
50 61 range of startup methods (SSH, local processes, PBS, mpiexec,
51 62 Windows HPC Server 2008). To start a cluster with 4 engines on your
52 local host simply do 'ipcluster start -n 4'. For more complex usage
53 you will typically do 'ipcluster create -p mycluster', then edit
54 configuration files, followed by 'ipcluster start -p mycluster -n 4'.
63 local host simply do 'ipcluster start n=4'. For more complex usage
64 you will typically do 'ipcluster create profile=mycluster', then edit
65 configuration files, followed by 'ipcluster start profile=mycluster n=4'.
55 66 """
56 67
57 68
@@ -72,426 +83,286 b' NO_CLUSTER = 12'
72 83
73 84
74 85 #-----------------------------------------------------------------------------
75 # Command line options
86 # Main application
76 87 #-----------------------------------------------------------------------------
88 start_help = """Start an IPython cluster for parallel computing
89
90 Start an ipython cluster by its profile name or cluster
91 directory. Cluster directories contain configuration, log and
92 security related files and are named using the convention
93 'profile_<name>' and should be creating using the 'start'
94 subcommand of 'ipcluster'. If your cluster directory is in
95 the cwd or the ipython directory, you can simply refer to it
96 using its profile name, 'ipcluster start n=4 profile=<profile>`,
97 otherwise use the 'profile_dir' option.
98 """
99 stop_help = """Stop a running IPython cluster
100
101 Stop a running ipython cluster by its profile name or cluster
102 directory. Cluster directories are named using the convention
103 'profile_<name>'. If your cluster directory is in
104 the cwd or the ipython directory, you can simply refer to it
105 using its profile name, 'ipcluster stop profile=<profile>`, otherwise
106 use the 'profile_dir' option.
107 """
108 engines_help = """Start engines connected to an existing IPython cluster
109
110 Start one or more engines to connect to an existing Cluster
111 by profile name or cluster directory.
112 Cluster directories contain configuration, log and
113 security related files and are named using the convention
114 'profile_<name>' and should be creating using the 'start'
115 subcommand of 'ipcluster'. If your cluster directory is in
116 the cwd or the ipython directory, you can simply refer to it
117 using its profile name, 'ipcluster engines n=4 profile=<profile>`,
118 otherwise use the 'profile_dir' option.
119 """
120 stop_aliases = dict(
121 signal='IPClusterStop.signal',
122 profile='BaseIPythonApplication.profile',
123 profile_dir='ProfileDir.location',
124 )
77 125
78
79 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
80
81 def _add_arguments(self):
82 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
83 # its defaults on self.parser. Instead, we will put those on
84 # default options on our subparsers.
85
86 # This has all the common options that all subcommands use
87 parent_parser1 = ArgumentParser(
88 add_help=False,
89 argument_default=SUPPRESS
90 )
91 self._add_ipython_dir(parent_parser1)
92 self._add_log_level(parent_parser1)
93
94 # This has all the common options that other subcommands use
95 parent_parser2 = ArgumentParser(
96 add_help=False,
97 argument_default=SUPPRESS
98 )
99 self._add_cluster_profile(parent_parser2)
100 self._add_cluster_dir(parent_parser2)
101 self._add_work_dir(parent_parser2)
102 paa = parent_parser2.add_argument
103 paa('--log-to-file',
104 action='store_true', dest='Global.log_to_file',
105 help='Log to a file in the log directory (default is stdout)')
106
107 # Create the object used to create the subparsers.
108 subparsers = self.parser.add_subparsers(
109 dest='Global.subcommand',
110 title='ipcluster subcommands',
111 description=
112 """ipcluster has a variety of subcommands. The general way of
113 running ipcluster is 'ipcluster <cmd> [options]'. To get help
114 on a particular subcommand do 'ipcluster <cmd> -h'."""
115 # help="For more help, type 'ipcluster <cmd> -h'",
116 )
117
118 # The "list" subcommand parser
119 parser_list = subparsers.add_parser(
120 'list',
121 parents=[parent_parser1],
122 argument_default=SUPPRESS,
123 help="List all clusters in cwd and ipython_dir.",
124 description=
125 """List all available clusters, by cluster directory, that can
126 be found in the current working directly or in the ipython
127 directory. Cluster directories are named using the convention
128 'cluster_<profile>'."""
129 )
130
131 # The "create" subcommand parser
132 parser_create = subparsers.add_parser(
133 'create',
134 parents=[parent_parser1, parent_parser2],
135 argument_default=SUPPRESS,
136 help="Create a new cluster directory.",
137 description=
138 """Create an ipython cluster directory by its profile name or
139 cluster directory path. Cluster directories contain
140 configuration, log and security related files and are named
141 using the convention 'cluster_<profile>'. By default they are
142 located in your ipython directory. Once created, you will
143 probably need to edit the configuration files in the cluster
144 directory to configure your cluster. Most users will create a
145 cluster directory by profile name,
146 'ipcluster create -p mycluster', which will put the directory
147 in '<ipython_dir>/cluster_mycluster'.
148 """
149 )
150 paa = parser_create.add_argument
151 paa('--reset-config',
152 dest='Global.reset_config', action='store_true',
153 help=
154 """Recopy the default config files to the cluster directory.
155 You will loose any modifications you have made to these files.""")
156
157 # The "start" subcommand parser
158 parser_start = subparsers.add_parser(
159 'start',
160 parents=[parent_parser1, parent_parser2],
161 argument_default=SUPPRESS,
162 help="Start a cluster.",
163 description=
164 """Start an ipython cluster by its profile name or cluster
165 directory. Cluster directories contain configuration, log and
166 security related files and are named using the convention
167 'cluster_<profile>' and should be creating using the 'start'
168 subcommand of 'ipcluster'. If your cluster directory is in
169 the cwd or the ipython directory, you can simply refer to it
170 using its profile name, 'ipcluster start -n 4 -p <profile>`,
171 otherwise use the '--cluster-dir' option.
172 """
173 )
126 class IPClusterStop(BaseParallelApplication):
127 name = u'ipcluster'
128 description = stop_help
129 config_file_name = Unicode(default_config_file_name)
130
131 signal = Int(signal.SIGINT, config=True,
132 help="signal to use for stopping processes.")
174 133
175 paa = parser_start.add_argument
176 paa('-n', '--number',
177 type=int, dest='Global.n',
178 help='The number of engines to start.',
179 metavar='Global.n')
180 paa('--clean-logs',
181 dest='Global.clean_logs', action='store_true',
182 help='Delete old log flies before starting.')
183 paa('--no-clean-logs',
184 dest='Global.clean_logs', action='store_false',
185 help="Don't delete old log flies before starting.")
186 paa('--daemon',
187 dest='Global.daemonize', action='store_true',
188 help='Daemonize the ipcluster program. This implies --log-to-file')
189 paa('--no-daemon',
190 dest='Global.daemonize', action='store_false',
191 help="Dont't daemonize the ipcluster program.")
192 paa('--delay',
193 type=float, dest='Global.delay',
194 help="Specify the delay (in seconds) between starting the controller and starting the engine(s).")
195
196 # The "stop" subcommand parser
197 parser_stop = subparsers.add_parser(
198 'stop',
199 parents=[parent_parser1, parent_parser2],
200 argument_default=SUPPRESS,
201 help="Stop a running cluster.",
202 description=
203 """Stop a running ipython cluster by its profile name or cluster
204 directory. Cluster directories are named using the convention
205 'cluster_<profile>'. If your cluster directory is in
206 the cwd or the ipython directory, you can simply refer to it
207 using its profile name, 'ipcluster stop -p <profile>`, otherwise
208 use the '--cluster-dir' option.
209 """
210 )
211 paa = parser_stop.add_argument
212 paa('--signal',
213 dest='Global.signal', type=int,
214 help="The signal number to use in stopping the cluster (default=2).",
215 metavar="Global.signal")
134 aliases = Dict(stop_aliases)
135
136 def start(self):
137 """Start the app for the stop subcommand."""
138 try:
139 pid = self.get_pid_from_file()
140 except PIDFileError:
141 self.log.critical(
142 'Could not read pid file, cluster is probably not running.'
143 )
144 # Here I exit with a unusual exit status that other processes
145 # can watch for to learn how I existed.
146 self.remove_pid_file()
147 self.exit(ALREADY_STOPPED)
216 148
217 # the "engines" subcommand parser
218 parser_engines = subparsers.add_parser(
219 'engines',
220 parents=[parent_parser1, parent_parser2],
221 argument_default=SUPPRESS,
222 help="Attach some engines to an existing controller or cluster.",
223 description=
224 """Start one or more engines to connect to an existing Cluster
225 by profile name or cluster directory.
226 Cluster directories contain configuration, log and
227 security related files and are named using the convention
228 'cluster_<profile>' and should be creating using the 'start'
229 subcommand of 'ipcluster'. If your cluster directory is in
230 the cwd or the ipython directory, you can simply refer to it
231 using its profile name, 'ipcluster engines -n 4 -p <profile>`,
232 otherwise use the '--cluster-dir' option.
233 """
234 )
235 paa = parser_engines.add_argument
236 paa('-n', '--number',
237 type=int, dest='Global.n',
238 help='The number of engines to start.',
239 metavar='Global.n')
240 paa('--daemon',
241 dest='Global.daemonize', action='store_true',
242 help='Daemonize the ipcluster program. This implies --log-to-file')
243 paa('--no-daemon',
244 dest='Global.daemonize', action='store_false',
245 help="Dont't daemonize the ipcluster program.")
246
247 #-----------------------------------------------------------------------------
248 # Main application
249 #-----------------------------------------------------------------------------
250
251
252 class IPClusterApp(ApplicationWithClusterDir):
149 if not self.check_pid(pid):
150 self.log.critical(
151 'Cluster [pid=%r] is not running.' % pid
152 )
153 self.remove_pid_file()
154 # Here I exit with a unusual exit status that other processes
155 # can watch for to learn how I existed.
156 self.exit(ALREADY_STOPPED)
157
158 elif os.name=='posix':
159 sig = self.signal
160 self.log.info(
161 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
162 )
163 try:
164 os.kill(pid, sig)
165 except OSError:
166 self.log.error("Stopping cluster failed, assuming already dead.",
167 exc_info=True)
168 self.remove_pid_file()
169 elif os.name=='nt':
170 try:
171 # kill the whole tree
172 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
173 except (CalledProcessError, OSError):
174 self.log.error("Stopping cluster failed, assuming already dead.",
175 exc_info=True)
176 self.remove_pid_file()
177
178 engine_aliases = {}
179 engine_aliases.update(base_aliases)
180 engine_aliases.update(dict(
181 n='IPClusterEngines.n',
182 elauncher = 'IPClusterEngines.engine_launcher_class',
183 ))
184 class IPClusterEngines(BaseParallelApplication):
253 185
254 186 name = u'ipcluster'
255 description = _description
187 description = engines_help
256 188 usage = None
257 command_line_loader = IPClusterAppConfigLoader
258 default_config_file_name = default_config_file_name
189 config_file_name = Unicode(default_config_file_name)
259 190 default_log_level = logging.INFO
260 auto_create_cluster_dir = False
261
262 def create_default_config(self):
263 super(IPClusterApp, self).create_default_config()
264 self.default_config.Global.controller_launcher = \
265 'IPython.parallel.apps.launcher.LocalControllerLauncher'
266 self.default_config.Global.engine_launcher = \
267 'IPython.parallel.apps.launcher.LocalEngineSetLauncher'
268 self.default_config.Global.n = 2
269 self.default_config.Global.delay = 2
270 self.default_config.Global.reset_config = False
271 self.default_config.Global.clean_logs = True
272 self.default_config.Global.signal = signal.SIGINT
273 self.default_config.Global.daemonize = False
274
275 def find_resources(self):
276 subcommand = self.command_line_config.Global.subcommand
277 if subcommand=='list':
278 self.list_cluster_dirs()
279 # Exit immediately because there is nothing left to do.
280 self.exit()
281 elif subcommand=='create':
282 self.auto_create_cluster_dir = True
283 super(IPClusterApp, self).find_resources()
284 elif subcommand=='start' or subcommand=='stop':
285 self.auto_create_cluster_dir = True
286 try:
287 super(IPClusterApp, self).find_resources()
288 except ClusterDirError:
289 raise ClusterDirError(
290 "Could not find a cluster directory. A cluster dir must "
291 "be created before running 'ipcluster start'. Do "
292 "'ipcluster create -h' or 'ipcluster list -h' for more "
293 "information about creating and listing cluster dirs."
294 )
295 elif subcommand=='engines':
296 self.auto_create_cluster_dir = False
297 try:
298 super(IPClusterApp, self).find_resources()
299 except ClusterDirError:
300 raise ClusterDirError(
301 "Could not find a cluster directory. A cluster dir must "
302 "be created before running 'ipcluster start'. Do "
303 "'ipcluster create -h' or 'ipcluster list -h' for more "
304 "information about creating and listing cluster dirs."
305 )
191 classes = List()
192 def _classes_default(self):
193 from IPython.parallel.apps import launcher
194 launchers = launcher.all_launchers
195 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
196 return [ProfileDir]+eslaunchers
197
198 n = Int(2, config=True,
199 help="The number of engines to start.")
306 200
307 def list_cluster_dirs(self):
308 # Find the search paths
309 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
310 if cluster_dir_paths:
311 cluster_dir_paths = cluster_dir_paths.split(':')
312 else:
313 cluster_dir_paths = []
314 try:
315 ipython_dir = self.command_line_config.Global.ipython_dir
316 except AttributeError:
317 ipython_dir = self.default_config.Global.ipython_dir
318 paths = [os.getcwd(), ipython_dir] + \
319 cluster_dir_paths
320 paths = list(set(paths))
321
322 self.log.info('Searching for cluster dirs in paths: %r' % paths)
323 for path in paths:
324 files = os.listdir(path)
325 for f in files:
326 full_path = os.path.join(path, f)
327 if os.path.isdir(full_path) and f.startswith('cluster_'):
328 profile = full_path.split('_')[-1]
329 start_cmd = 'ipcluster start -p %s -n 4' % profile
330 print start_cmd + " ==> " + full_path
331
332 def pre_construct(self):
333 # IPClusterApp.pre_construct() is where we cd to the working directory.
334 super(IPClusterApp, self).pre_construct()
335 config = self.master_config
336 try:
337 daemon = config.Global.daemonize
338 if daemon:
339 config.Global.log_to_file = True
340 except AttributeError:
341 pass
201 engine_launcher_class = Unicode('LocalEngineSetLauncher',
202 config=True,
203 help="The class for launching a set of Engines."
204 )
205 daemonize = Bool(False, config=True,
206 help='Daemonize the ipcluster program. This implies --log-to-file')
342 207
343 def construct(self):
344 config = self.master_config
345 subcmd = config.Global.subcommand
346 reset = config.Global.reset_config
347 if subcmd == 'list':
348 return
349 if subcmd == 'create':
350 self.log.info('Copying default config files to cluster directory '
351 '[overwrite=%r]' % (reset,))
352 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
353 if subcmd =='start':
354 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
355 self.start_logging()
356 self.loop = ioloop.IOLoop.instance()
357 # reactor.callWhenRunning(self.start_launchers)
358 dc = ioloop.DelayedCallback(self.start_launchers, 0, self.loop)
359 dc.start()
360 if subcmd == 'engines':
361 self.start_logging()
362 self.loop = ioloop.IOLoop.instance()
363 # reactor.callWhenRunning(self.start_launchers)
364 engine_only = lambda : self.start_launchers(controller=False)
365 dc = ioloop.DelayedCallback(engine_only, 0, self.loop)
366 dc.start()
208 def _daemonize_changed(self, name, old, new):
209 if new:
210 self.log_to_file = True
367 211
368 def start_launchers(self, controller=True):
369 config = self.master_config
370
371 # Create the launchers. In both bases, we set the work_dir of
372 # the launcher to the cluster_dir. This is where the launcher's
373 # subprocesses will be launched. It is not where the controller
374 # and engine will be launched.
375 if controller:
376 cl_class = import_item(config.Global.controller_launcher)
377 self.controller_launcher = cl_class(
378 work_dir=self.cluster_dir, config=config,
379 logname=self.log.name
380 )
381 # Setup the observing of stopping. If the controller dies, shut
382 # everything down as that will be completely fatal for the engines.
383 self.controller_launcher.on_stop(self.stop_launchers)
384 # But, we don't monitor the stopping of engines. An engine dying
385 # is just fine and in principle a user could start a new engine.
386 # Also, if we did monitor engine stopping, it is difficult to
387 # know what to do when only some engines die. Currently, the
388 # observing of engine stopping is inconsistent. Some launchers
389 # might trigger on a single engine stopping, other wait until
390 # all stop. TODO: think more about how to handle this.
391 else:
392 self.controller_launcher = None
393
394 el_class = import_item(config.Global.engine_launcher)
395 self.engine_launcher = el_class(
396 work_dir=self.cluster_dir, config=config, logname=self.log.name
397 )
212 aliases = Dict(engine_aliases)
213 # flags = Dict(flags)
214 _stopping = False
398 215
216 def initialize(self, argv=None):
217 super(IPClusterEngines, self).initialize(argv)
218 self.init_signal()
219 self.init_launchers()
220
221 def init_launchers(self):
222 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
223 self.engine_launcher.on_stop(lambda r: self.loop.stop())
224
225 def init_signal(self):
399 226 # Setup signals
400 227 signal.signal(signal.SIGINT, self.sigint_handler)
401
402 # Start the controller and engines
403 self._stopping = False # Make sure stop_launchers is not called 2x.
404 if controller:
405 self.start_controller()
406 dc = ioloop.DelayedCallback(self.start_engines, 1000*config.Global.delay*controller, self.loop)
407 dc.start()
408 self.startup_message()
409
410 def startup_message(self, r=None):
411 self.log.info("IPython cluster: started")
412 return r
413
414 def start_controller(self, r=None):
415 # self.log.info("In start_controller")
416 config = self.master_config
417 d = self.controller_launcher.start(
418 cluster_dir=config.Global.cluster_dir
228
229 def build_launcher(self, clsname):
230 """import and instantiate a Launcher based on importstring"""
231 if '.' not in clsname:
232 # not a module, presume it's the raw name in apps.launcher
233 clsname = 'IPython.parallel.apps.launcher.'+clsname
234 # print repr(clsname)
235 klass = import_item(clsname)
236
237 launcher = klass(
238 work_dir=self.profile_dir.location, config=self.config, log=self.log
419 239 )
420 return d
421
422 def start_engines(self, r=None):
423 # self.log.info("In start_engines")
424 config = self.master_config
425
426 d = self.engine_launcher.start(
427 config.Global.n,
428 cluster_dir=config.Global.cluster_dir
240 return launcher
241
242 def start_engines(self):
243 self.log.info("Starting %i engines"%self.n)
244 self.engine_launcher.start(
245 self.n,
246 self.profile_dir.location
429 247 )
430 return d
431
432 def stop_controller(self, r=None):
433 # self.log.info("In stop_controller")
434 if self.controller_launcher and self.controller_launcher.running:
435 return self.controller_launcher.stop()
436 248
437 def stop_engines(self, r=None):
438 # self.log.info("In stop_engines")
249 def stop_engines(self):
250 self.log.info("Stopping Engines...")
439 251 if self.engine_launcher.running:
440 252 d = self.engine_launcher.stop()
441 # d.addErrback(self.log_err)
442 253 return d
443 254 else:
444 255 return None
445 256
446 def log_err(self, f):
447 self.log.error(f.getTraceback())
448 return None
449
450 257 def stop_launchers(self, r=None):
451 258 if not self._stopping:
452 259 self._stopping = True
453 # if isinstance(r, failure.Failure):
454 # self.log.error('Unexpected error in ipcluster:')
455 # self.log.info(r.getTraceback())
456 260 self.log.error("IPython cluster: stopping")
457 # These return deferreds. We are not doing anything with them
458 # but we are holding refs to them as a reminder that they
459 # do return deferreds.
460 d1 = self.stop_engines()
461 d2 = self.stop_controller()
261 self.stop_engines()
462 262 # Wait a few seconds to let things shut down.
463 263 dc = ioloop.DelayedCallback(self.loop.stop, 4000, self.loop)
464 264 dc.start()
465 # reactor.callLater(4.0, reactor.stop)
466 265
467 266 def sigint_handler(self, signum, frame):
267 self.log.debug("SIGINT received, stopping launchers...")
468 268 self.stop_launchers()
469 269
470 270 def start_logging(self):
471 271 # Remove old log files of the controller and engine
472 if self.master_config.Global.clean_logs:
473 log_dir = self.master_config.Global.log_dir
272 if self.clean_logs:
273 log_dir = self.profile_dir.log_dir
474 274 for f in os.listdir(log_dir):
475 275 if re.match(r'ip(engine|controller)z-\d+\.(log|err|out)',f):
476 276 os.remove(os.path.join(log_dir, f))
477 277 # This will remove old log files for ipcluster itself
478 super(IPClusterApp, self).start_logging()
479
480 def start_app(self):
481 """Start the application, depending on what subcommand is used."""
482 subcmd = self.master_config.Global.subcommand
483 if subcmd=='create' or subcmd=='list':
484 return
485 elif subcmd=='start':
486 self.start_app_start()
487 elif subcmd=='stop':
488 self.start_app_stop()
489 elif subcmd=='engines':
490 self.start_app_engines()
491
492 def start_app_start(self):
278 # super(IPBaseParallelApplication, self).start_logging()
279
280 def start(self):
281 """Start the app for the engines subcommand."""
282 self.log.info("IPython cluster: started")
283 # First see if the cluster is already running
284
285 # Now log and daemonize
286 self.log.info(
287 'Starting engines with [daemon=%r]' % self.daemonize
288 )
289 # TODO: Get daemonize working on Windows or as a Windows Server.
290 if self.daemonize:
291 if os.name=='posix':
292 daemonize()
293
294 dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop)
295 dc.start()
296 # Now write the new pid file AFTER our new forked pid is active.
297 # self.write_pid_file()
298 try:
299 self.loop.start()
300 except KeyboardInterrupt:
301 pass
302 except zmq.ZMQError as e:
303 if e.errno == errno.EINTR:
304 pass
305 else:
306 raise
307
308 start_aliases = {}
309 start_aliases.update(engine_aliases)
310 start_aliases.update(dict(
311 delay='IPClusterStart.delay',
312 clean_logs='IPClusterStart.clean_logs',
313 ))
314
315 class IPClusterStart(IPClusterEngines):
316
317 name = u'ipcluster'
318 description = start_help
319 default_log_level = logging.INFO
320 auto_create = Bool(True, config=True,
321 help="whether to create the profile_dir if it doesn't exist")
322 classes = List()
323 def _classes_default(self,):
324 from IPython.parallel.apps import launcher
325 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
326
327 clean_logs = Bool(True, config=True,
328 help="whether to cleanup old logs before starting")
329
330 delay = CFloat(1., config=True,
331 help="delay (in s) between starting the controller and the engines")
332
333 controller_launcher_class = Unicode('LocalControllerLauncher',
334 config=True,
335 help="The class for launching a Controller."
336 )
337 reset = Bool(False, config=True,
338 help="Whether to reset config files as part of '--create'."
339 )
340
341 # flags = Dict(flags)
342 aliases = Dict(start_aliases)
343
344 def init_launchers(self):
345 self.controller_launcher = self.build_launcher(self.controller_launcher_class)
346 self.engine_launcher = self.build_launcher(self.engine_launcher_class)
347 self.controller_launcher.on_stop(self.stop_launchers)
348
349 def start_controller(self):
350 self.controller_launcher.start(
351 self.profile_dir.location
352 )
353
354 def stop_controller(self):
355 # self.log.info("In stop_controller")
356 if self.controller_launcher and self.controller_launcher.running:
357 return self.controller_launcher.stop()
358
359 def stop_launchers(self, r=None):
360 if not self._stopping:
361 self.stop_controller()
362 super(IPClusterStart, self).stop_launchers()
363
364 def start(self):
493 365 """Start the app for the start subcommand."""
494 config = self.master_config
495 366 # First see if the cluster is already running
496 367 try:
497 368 pid = self.get_pid_from_file()
@@ -512,14 +383,17 b' class IPClusterApp(ApplicationWithClusterDir):'
512 383
513 384 # Now log and daemonize
514 385 self.log.info(
515 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
386 'Starting ipcluster with [daemon=%r]' % self.daemonize
516 387 )
517 388 # TODO: Get daemonize working on Windows or as a Windows Server.
518 if config.Global.daemonize:
389 if self.daemonize:
519 390 if os.name=='posix':
520 from twisted.scripts._twistd_unix import daemonize
521 391 daemonize()
522 392
393 dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop)
394 dc.start()
395 dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop)
396 dc.start()
523 397 # Now write the new pid file AFTER our new forked pid is active.
524 398 self.write_pid_file()
525 399 try:
@@ -534,81 +408,36 b' class IPClusterApp(ApplicationWithClusterDir):'
534 408 finally:
535 409 self.remove_pid_file()
536 410
537 def start_app_engines(self):
538 """Start the app for the start subcommand."""
539 config = self.master_config
540 # First see if the cluster is already running
541
542 # Now log and daemonize
543 self.log.info(
544 'Starting engines with [daemon=%r]' % config.Global.daemonize
545 )
546 # TODO: Get daemonize working on Windows or as a Windows Server.
547 if config.Global.daemonize:
548 if os.name=='posix':
549 from twisted.scripts._twistd_unix import daemonize
550 daemonize()
411 base='IPython.parallel.apps.ipclusterapp.IPCluster'
551 412
552 # Now write the new pid file AFTER our new forked pid is active.
553 # self.write_pid_file()
554 try:
555 self.loop.start()
556 except KeyboardInterrupt:
557 pass
558 except zmq.ZMQError as e:
559 if e.errno == errno.EINTR:
560 pass
561 else:
562 raise
563 # self.remove_pid_file()
413 class IPClusterApp(Application):
414 name = u'ipcluster'
415 description = _description
564 416
565 def start_app_stop(self):
566 """Start the app for the stop subcommand."""
567 config = self.master_config
568 try:
569 pid = self.get_pid_from_file()
570 except PIDFileError:
571 self.log.critical(
572 'Could not read pid file, cluster is probably not running.'
573 )
574 # Here I exit with a unusual exit status that other processes
575 # can watch for to learn how I existed.
576 self.remove_pid_file()
577 self.exit(ALREADY_STOPPED)
578
579 if not self.check_pid(pid):
580 self.log.critical(
581 'Cluster [pid=%r] is not running.' % pid
582 )
583 self.remove_pid_file()
584 # Here I exit with a unusual exit status that other processes
585 # can watch for to learn how I existed.
586 self.exit(ALREADY_STOPPED)
587
588 elif os.name=='posix':
589 sig = config.Global.signal
590 self.log.info(
591 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
592 )
593 try:
594 os.kill(pid, sig)
595 except OSError:
596 self.log.error("Stopping cluster failed, assuming already dead.",
597 exc_info=True)
598 self.remove_pid_file()
599 elif os.name=='nt':
600 try:
601 # kill the whole tree
602 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
603 except (CalledProcessError, OSError):
604 self.log.error("Stopping cluster failed, assuming already dead.",
605 exc_info=True)
606 self.remove_pid_file()
607
417 subcommands = {
418 'start' : (base+'Start', start_help),
419 'stop' : (base+'Stop', stop_help),
420 'engines' : (base+'Engines', engines_help),
421 }
422
423 # no aliases or flags for parent App
424 aliases = Dict()
425 flags = Dict()
426
427 def start(self):
428 if self.subapp is None:
429 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
430 print
431 self.print_description()
432 self.print_subcommands()
433 self.exit(1)
434 else:
435 return self.subapp.start()
608 436
609 437 def launch_new_instance():
610 438 """Create and run the IPython cluster."""
611 app = IPClusterApp()
439 app = IPClusterApp.instance()
440 app.initialize()
612 441 app.start()
613 442
614 443
This diff has been collapsed as it changes many lines, (509 lines changed) Show them Hide them
@@ -2,10 +2,16 b''
2 2 # encoding: utf-8
3 3 """
4 4 The IPython controller application.
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
5 11 """
6 12
7 13 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
9 15 #
10 16 # Distributed under the terms of the BSD License. The full license is in
11 17 # the file COPYING, distributed as part of this software.
@@ -17,31 +23,46 b' The IPython controller application.'
17 23
18 24 from __future__ import with_statement
19 25
20 import copy
21 26 import os
22 import logging
23 27 import socket
24 28 import stat
25 29 import sys
26 30 import uuid
27 31
32 from multiprocessing import Process
33
28 34 import zmq
35 from zmq.devices import ProcessMonitoredQueue
29 36 from zmq.log.handlers import PUBHandler
30 37 from zmq.utils import jsonapi as json
31 38
32 from IPython.config.loader import Config
33
34 from IPython.parallel import factory
39 from IPython.config.application import boolean_flag
40 from IPython.core.profiledir import ProfileDir
35 41
36 from IPython.parallel.apps.clusterdir import (
37 ApplicationWithClusterDir,
38 ClusterDirConfigLoader
42 from IPython.parallel.apps.baseapp import (
43 BaseParallelApplication,
44 base_flags
39 45 )
40 from IPython.parallel.util import disambiguate_ip_address, split_url
41 # from IPython.kernel.fcutil import FCServiceFactory, FURLError
42 from IPython.utils.traitlets import Instance, Unicode
46 from IPython.utils.importstring import import_item
47 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict
48
49 # from IPython.parallel.controller.controller import ControllerFactory
50 from IPython.zmq.session import Session
51 from IPython.parallel.controller.heartmonitor import HeartMonitor
52 from IPython.parallel.controller.hub import HubFactory
53 from IPython.parallel.controller.scheduler import TaskScheduler,launch_scheduler
54 from IPython.parallel.controller.sqlitedb import SQLiteDB
43 55
44 from IPython.parallel.controller.controller import ControllerFactory
56 from IPython.parallel.util import signal_children, split_url
57
58 # conditional import of MongoDB backend class
59
60 try:
61 from IPython.parallel.controller.mongodb import MongoDB
62 except ImportError:
63 maybe_mongo = []
64 else:
65 maybe_mongo = [MongoDB]
45 66
46 67
47 68 #-----------------------------------------------------------------------------
@@ -59,238 +80,112 b' The IPython controller provides a gateway between the IPython engines and'
59 80 clients. The controller needs to be started before the engines and can be
60 81 configured using command line options or using a cluster directory. Cluster
61 82 directories contain config, log and security files and are usually located in
62 your ipython directory and named as "cluster_<profile>". See the --profile
63 and --cluster-dir options for details.
83 your ipython directory and named as "profile_name". See the `profile`
84 and `profile_dir` options for details.
64 85 """
65 86
66 #-----------------------------------------------------------------------------
67 # Default interfaces
68 #-----------------------------------------------------------------------------
69
70 # The default client interfaces for FCClientServiceFactory.interfaces
71 default_client_interfaces = Config()
72 default_client_interfaces.Default.url_file = 'ipcontroller-client.url'
73
74 # Make this a dict we can pass to Config.__init__ for the default
75 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
76 87
77 88
78 89
79 # The default engine interfaces for FCEngineServiceFactory.interfaces
80 default_engine_interfaces = Config()
81 default_engine_interfaces.Default.url_file = u'ipcontroller-engine.url'
82
83 # Make this a dict we can pass to Config.__init__ for the default
84 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
85
86
87 #-----------------------------------------------------------------------------
88 # Service factories
89 #-----------------------------------------------------------------------------
90
91 #
92 # class FCClientServiceFactory(FCServiceFactory):
93 # """A Foolscap implementation of the client services."""
94 #
95 # cert_file = Unicode(u'ipcontroller-client.pem', config=True)
96 # interfaces = Instance(klass=Config, kw=default_client_interfaces,
97 # allow_none=False, config=True)
98 #
99 #
100 # class FCEngineServiceFactory(FCServiceFactory):
101 # """A Foolscap implementation of the engine services."""
102 #
103 # cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
104 # interfaces = Instance(klass=dict, kw=default_engine_interfaces,
105 # allow_none=False, config=True)
106 #
107
108 #-----------------------------------------------------------------------------
109 # Command line options
110 #-----------------------------------------------------------------------------
111
112
113 class IPControllerAppConfigLoader(ClusterDirConfigLoader):
114
115 def _add_arguments(self):
116 super(IPControllerAppConfigLoader, self)._add_arguments()
117 paa = self.parser.add_argument
118
119 ## Hub Config:
120 paa('--mongodb',
121 dest='HubFactory.db_class', action='store_const',
122 const='IPython.parallel.controller.mongodb.MongoDB',
123 help='Use MongoDB for task storage [default: in-memory]')
124 paa('--sqlite',
125 dest='HubFactory.db_class', action='store_const',
126 const='IPython.parallel.controller.sqlitedb.SQLiteDB',
127 help='Use SQLite3 for DB task storage [default: in-memory]')
128 paa('--hb',
129 type=int, dest='HubFactory.hb', nargs=2,
130 help='The (2) ports the Hub\'s Heartmonitor will use for the heartbeat '
131 'connections [default: random]',
132 metavar='Hub.hb_ports')
133 paa('--ping',
134 type=int, dest='HubFactory.ping',
135 help='The frequency at which the Hub pings the engines for heartbeats '
136 ' (in ms) [default: 100]',
137 metavar='Hub.ping')
138
139 # Client config
140 paa('--client-ip',
141 type=str, dest='HubFactory.client_ip',
142 help='The IP address or hostname the Hub will listen on for '
143 'client connections. Both engine-ip and client-ip can be set simultaneously '
144 'via --ip [default: loopback]',
145 metavar='Hub.client_ip')
146 paa('--client-transport',
147 type=str, dest='HubFactory.client_transport',
148 help='The ZeroMQ transport the Hub will use for '
149 'client connections. Both engine-transport and client-transport can be set simultaneously '
150 'via --transport [default: tcp]',
151 metavar='Hub.client_transport')
152 paa('--query',
153 type=int, dest='HubFactory.query_port',
154 help='The port on which the Hub XREP socket will listen for result queries from clients [default: random]',
155 metavar='Hub.query_port')
156 paa('--notifier',
157 type=int, dest='HubFactory.notifier_port',
158 help='The port on which the Hub PUB socket will listen for notification connections [default: random]',
159 metavar='Hub.notifier_port')
160
161 # Engine config
162 paa('--engine-ip',
163 type=str, dest='HubFactory.engine_ip',
164 help='The IP address or hostname the Hub will listen on for '
165 'engine connections. This applies to the Hub and its schedulers'
166 'engine-ip and client-ip can be set simultaneously '
167 'via --ip [default: loopback]',
168 metavar='Hub.engine_ip')
169 paa('--engine-transport',
170 type=str, dest='HubFactory.engine_transport',
171 help='The ZeroMQ transport the Hub will use for '
172 'client connections. Both engine-transport and client-transport can be set simultaneously '
173 'via --transport [default: tcp]',
174 metavar='Hub.engine_transport')
175
176 # Scheduler config
177 paa('--mux',
178 type=int, dest='ControllerFactory.mux', nargs=2,
179 help='The (2) ports the MUX scheduler will listen on for client,engine '
180 'connections, respectively [default: random]',
181 metavar='Scheduler.mux_ports')
182 paa('--task',
183 type=int, dest='ControllerFactory.task', nargs=2,
184 help='The (2) ports the Task scheduler will listen on for client,engine '
185 'connections, respectively [default: random]',
186 metavar='Scheduler.task_ports')
187 paa('--control',
188 type=int, dest='ControllerFactory.control', nargs=2,
189 help='The (2) ports the Control scheduler will listen on for client,engine '
190 'connections, respectively [default: random]',
191 metavar='Scheduler.control_ports')
192 paa('--iopub',
193 type=int, dest='ControllerFactory.iopub', nargs=2,
194 help='The (2) ports the IOPub scheduler will listen on for client,engine '
195 'connections, respectively [default: random]',
196 metavar='Scheduler.iopub_ports')
197
198 paa('--scheme',
199 type=str, dest='HubFactory.scheme',
200 choices = ['pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'],
201 help='select the task scheduler scheme [default: Python LRU]',
202 metavar='Scheduler.scheme')
203 paa('--usethreads',
204 dest='ControllerFactory.usethreads', action="store_true",
205 help='Use threads instead of processes for the schedulers',
206 )
207 paa('--hwm',
208 dest='TaskScheduler.hwm', type=int,
209 help='specify the High Water Mark (HWM) '
210 'in the Python scheduler. This is the maximum number '
211 'of allowed outstanding tasks on each engine.',
212 )
213
214 ## Global config
215 paa('--log-to-file',
216 action='store_true', dest='Global.log_to_file',
217 help='Log to a file in the log directory (default is stdout)')
218 paa('--log-url',
219 type=str, dest='Global.log_url',
220 help='Broadcast logs to an iploggerz process [default: disabled]')
221 paa('-r','--reuse-files',
222 action='store_true', dest='Global.reuse_files',
223 help='Try to reuse existing json connection files.')
224 paa('--no-secure',
225 action='store_false', dest='Global.secure',
226 help='Turn off execution keys (default).')
227 paa('--secure',
228 action='store_true', dest='Global.secure',
229 help='Turn on execution keys.')
230 paa('--execkey',
231 type=str, dest='Global.exec_key',
232 help='path to a file containing an execution key.',
233 metavar='keyfile')
234 paa('--ssh',
235 type=str, dest='Global.sshserver',
236 help='ssh url for clients to use when connecting to the Controller '
237 'processes. It should be of the form: [user@]server[:port]. The '
238 'Controller\'s listening addresses must be accessible from the ssh server',
239 metavar='Global.sshserver')
240 paa('--location',
241 type=str, dest='Global.location',
242 help="The external IP or domain name of this machine, used for disambiguating "
243 "engine and client connections.",
244 metavar='Global.location')
245 factory.add_session_arguments(self.parser)
246 factory.add_registration_arguments(self.parser)
247
248
249 90 #-----------------------------------------------------------------------------
250 91 # The main application
251 92 #-----------------------------------------------------------------------------
252
253
254 class IPControllerApp(ApplicationWithClusterDir):
93 flags = {}
94 flags.update(base_flags)
95 flags.update({
96 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
97 'Use threads instead of processes for the schedulers'),
98 'sqlitedb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.sqlitedb.SQLiteDB'}},
99 'use the SQLiteDB backend'),
100 'mongodb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.mongodb.MongoDB'}},
101 'use the MongoDB backend'),
102 'dictdb' : ({'HubFactory' : {'db_class' : 'IPython.parallel.controller.dictdb.DictDB'}},
103 'use the in-memory DictDB backend'),
104 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
105 'reuse existing json connection files')
106 })
107
108 flags.update(boolean_flag('secure', 'IPControllerApp.secure',
109 "Use HMAC digests for authentication of messages.",
110 "Don't authenticate messages."
111 ))
112
113 class IPControllerApp(BaseParallelApplication):
255 114
256 115 name = u'ipcontroller'
257 116 description = _description
258 command_line_loader = IPControllerAppConfigLoader
259 default_config_file_name = default_config_file_name
260 auto_create_cluster_dir = True
117 config_file_name = Unicode(default_config_file_name)
118 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, SQLiteDB] + maybe_mongo
261 119
262
263 def create_default_config(self):
264 super(IPControllerApp, self).create_default_config()
265 # Don't set defaults for Global.secure or Global.reuse_furls
266 # as those are set in a component.
267 self.default_config.Global.import_statements = []
268 self.default_config.Global.clean_logs = True
269 self.default_config.Global.secure = True
270 self.default_config.Global.reuse_files = False
271 self.default_config.Global.exec_key = "exec_key.key"
272 self.default_config.Global.sshserver = None
273 self.default_config.Global.location = None
274
275 def pre_construct(self):
276 super(IPControllerApp, self).pre_construct()
277 c = self.master_config
278 # The defaults for these are set in FCClientServiceFactory and
279 # FCEngineServiceFactory, so we only set them here if the global
280 # options have be set to override the class level defaults.
120 # change default to True
121 auto_create = Bool(True, config=True,
122 help="""Whether to create profile dir if it doesn't exist.""")
123
124 reuse_files = Bool(False, config=True,
125 help='Whether to reuse existing json connection files.'
126 )
127 secure = Bool(True, config=True,
128 help='Whether to use HMAC digests for extra message authentication.'
129 )
130 ssh_server = Unicode(u'', config=True,
131 help="""ssh url for clients to use when connecting to the Controller
132 processes. It should be of the form: [user@]server[:port]. The
133 Controller's listening addresses must be accessible from the ssh server""",
134 )
135 location = Unicode(u'', config=True,
136 help="""The external IP or domain name of the Controller, used for disambiguating
137 engine and client connections.""",
138 )
139 import_statements = List([], config=True,
140 help="import statements to be run at startup. Necessary in some environments"
141 )
142
143 use_threads = Bool(False, config=True,
144 help='Use threads instead of processes for the schedulers',
145 )
146
147 # internal
148 children = List()
149 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
150
151 def _use_threads_changed(self, name, old, new):
152 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
153
154 aliases = Dict(dict(
155 log_level = 'IPControllerApp.log_level',
156 log_url = 'IPControllerApp.log_url',
157 reuse_files = 'IPControllerApp.reuse_files',
158 secure = 'IPControllerApp.secure',
159 ssh = 'IPControllerApp.ssh_server',
160 use_threads = 'IPControllerApp.use_threads',
161 import_statements = 'IPControllerApp.import_statements',
162 location = 'IPControllerApp.location',
163
164 ident = 'Session.session',
165 user = 'Session.username',
166 exec_key = 'Session.keyfile',
167
168 url = 'HubFactory.url',
169 ip = 'HubFactory.ip',
170 transport = 'HubFactory.transport',
171 port = 'HubFactory.regport',
172
173 ping = 'HeartMonitor.period',
174
175 scheme = 'TaskScheduler.scheme_name',
176 hwm = 'TaskScheduler.hwm',
177
178
179 profile = "BaseIPythonApplication.profile",
180 profile_dir = 'ProfileDir.location',
281 181
282 # if hasattr(c.Global, 'reuse_furls'):
283 # c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
284 # c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
285 # del c.Global.reuse_furls
286 # if hasattr(c.Global, 'secure'):
287 # c.FCClientServiceFactory.secure = c.Global.secure
288 # c.FCEngineServiceFactory.secure = c.Global.secure
289 # del c.Global.secure
182 ))
183 flags = Dict(flags)
290 184
185
291 186 def save_connection_dict(self, fname, cdict):
292 187 """save a connection dict to json file."""
293 c = self.master_config
188 c = self.config
294 189 url = cdict['url']
295 190 location = cdict['location']
296 191 if not location:
@@ -301,43 +196,41 b' class IPControllerApp(ApplicationWithClusterDir):'
301 196 else:
302 197 location = socket.gethostbyname_ex(socket.gethostname())[2][-1]
303 198 cdict['location'] = location
304 fname = os.path.join(c.Global.security_dir, fname)
199 fname = os.path.join(self.profile_dir.security_dir, fname)
305 200 with open(fname, 'w') as f:
306 201 f.write(json.dumps(cdict, indent=2))
307 202 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
308 203
309 204 def load_config_from_json(self):
310 205 """load config from existing json connector files."""
311 c = self.master_config
206 c = self.config
312 207 # load from engine config
313 with open(os.path.join(c.Global.security_dir, 'ipcontroller-engine.json')) as f:
208 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-engine.json')) as f:
314 209 cfg = json.loads(f.read())
315 key = c.SessionFactory.exec_key = cfg['exec_key']
210 key = c.Session.key = cfg['exec_key']
316 211 xport,addr = cfg['url'].split('://')
317 212 c.HubFactory.engine_transport = xport
318 213 ip,ports = addr.split(':')
319 214 c.HubFactory.engine_ip = ip
320 215 c.HubFactory.regport = int(ports)
321 c.Global.location = cfg['location']
216 self.location = cfg['location']
322 217
323 218 # load client config
324 with open(os.path.join(c.Global.security_dir, 'ipcontroller-client.json')) as f:
219 with open(os.path.join(self.profile_dir.security_dir, 'ipcontroller-client.json')) as f:
325 220 cfg = json.loads(f.read())
326 221 assert key == cfg['exec_key'], "exec_key mismatch between engine and client keys"
327 222 xport,addr = cfg['url'].split('://')
328 223 c.HubFactory.client_transport = xport
329 224 ip,ports = addr.split(':')
330 225 c.HubFactory.client_ip = ip
331 c.Global.sshserver = cfg['ssh']
226 self.ssh_server = cfg['ssh']
332 227 assert int(ports) == c.HubFactory.regport, "regport mismatch"
333 228
334 def construct(self):
335 # This is the working dir by now.
336 sys.path.insert(0, '')
337 c = self.master_config
229 def init_hub(self):
230 c = self.config
338 231
339 self.import_statements()
340 reusing = c.Global.reuse_files
232 self.do_import_statements()
233 reusing = self.reuse_files
341 234 if reusing:
342 235 try:
343 236 self.load_config_from_json()
@@ -346,21 +239,20 b' class IPControllerApp(ApplicationWithClusterDir):'
346 239 # check again, because reusing may have failed:
347 240 if reusing:
348 241 pass
349 elif c.Global.secure:
350 keyfile = os.path.join(c.Global.security_dir, c.Global.exec_key)
242 elif self.secure:
351 243 key = str(uuid.uuid4())
352 with open(keyfile, 'w') as f:
353 f.write(key)
354 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
355 c.SessionFactory.exec_key = key
244 # keyfile = os.path.join(self.profile_dir.security_dir, self.exec_key)
245 # with open(keyfile, 'w') as f:
246 # f.write(key)
247 # os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
248 c.Session.key = key
356 249 else:
357 c.SessionFactory.exec_key = ''
358 key = None
250 key = c.Session.key = ''
359 251
360 252 try:
361 self.factory = ControllerFactory(config=c, logname=self.log.name)
362 self.start_logging()
363 self.factory.construct()
253 self.factory = HubFactory(config=c, log=self.log)
254 # self.start_logging()
255 self.factory.init_hub()
364 256 except:
365 257 self.log.error("Couldn't construct the Controller", exc_info=True)
366 258 self.exit(1)
@@ -369,21 +261,82 b' class IPControllerApp(ApplicationWithClusterDir):'
369 261 # save to new json config files
370 262 f = self.factory
371 263 cdict = {'exec_key' : key,
372 'ssh' : c.Global.sshserver,
264 'ssh' : self.ssh_server,
373 265 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport),
374 'location' : c.Global.location
266 'location' : self.location
375 267 }
376 268 self.save_connection_dict('ipcontroller-client.json', cdict)
377 269 edict = cdict
378 270 edict['url']="%s://%s:%s"%((f.client_transport, f.client_ip, f.regport))
379 271 self.save_connection_dict('ipcontroller-engine.json', edict)
272
273 #
274 def init_schedulers(self):
275 children = self.children
276 mq = import_item(str(self.mq_class))
380 277
278 hub = self.factory
279 # maybe_inproc = 'inproc://monitor' if self.use_threads else self.monitor_url
280 # IOPub relay (in a Process)
281 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, 'N/A','iopub')
282 q.bind_in(hub.client_info['iopub'])
283 q.bind_out(hub.engine_info['iopub'])
284 q.setsockopt_out(zmq.SUBSCRIBE, '')
285 q.connect_mon(hub.monitor_url)
286 q.daemon=True
287 children.append(q)
288
289 # Multiplexer Queue (in a Process)
290 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, 'in', 'out')
291 q.bind_in(hub.client_info['mux'])
292 q.setsockopt_in(zmq.IDENTITY, 'mux')
293 q.bind_out(hub.engine_info['mux'])
294 q.connect_mon(hub.monitor_url)
295 q.daemon=True
296 children.append(q)
297
298 # Control Queue (in a Process)
299 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, 'incontrol', 'outcontrol')
300 q.bind_in(hub.client_info['control'])
301 q.setsockopt_in(zmq.IDENTITY, 'control')
302 q.bind_out(hub.engine_info['control'])
303 q.connect_mon(hub.monitor_url)
304 q.daemon=True
305 children.append(q)
306 try:
307 scheme = self.config.TaskScheduler.scheme_name
308 except AttributeError:
309 scheme = TaskScheduler.scheme_name.get_default_value()
310 # Task Queue (in a Process)
311 if scheme == 'pure':
312 self.log.warn("task::using pure XREQ Task scheduler")
313 q = mq(zmq.XREP, zmq.XREQ, zmq.PUB, 'intask', 'outtask')
314 # q.setsockopt_out(zmq.HWM, hub.hwm)
315 q.bind_in(hub.client_info['task'][1])
316 q.setsockopt_in(zmq.IDENTITY, 'task')
317 q.bind_out(hub.engine_info['task'])
318 q.connect_mon(hub.monitor_url)
319 q.daemon=True
320 children.append(q)
321 elif scheme == 'none':
322 self.log.warn("task::using no Task scheduler")
323
324 else:
325 self.log.info("task::using Python %s Task scheduler"%scheme)
326 sargs = (hub.client_info['task'][1], hub.engine_info['task'],
327 hub.monitor_url, hub.client_info['notification'])
328 kwargs = dict(logname='scheduler', loglevel=self.log_level,
329 log_url = self.log_url, config=dict(self.config))
330 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
331 q.daemon=True
332 children.append(q)
333
381 334
382 335 def save_urls(self):
383 336 """save the registration urls to files."""
384 c = self.master_config
337 c = self.config
385 338
386 sec_dir = c.Global.security_dir
339 sec_dir = self.profile_dir.security_dir
387 340 cf = self.factory
388 341
389 342 with open(os.path.join(sec_dir, 'ipcontroller-engine.url'), 'w') as f:
@@ -393,8 +346,8 b' class IPControllerApp(ApplicationWithClusterDir):'
393 346 f.write("%s://%s:%s"%(cf.client_transport, cf.client_ip, cf.regport))
394 347
395 348
396 def import_statements(self):
397 statements = self.master_config.Global.import_statements
349 def do_import_statements(self):
350 statements = self.import_statements
398 351 for s in statements:
399 352 try:
400 353 self.log.msg("Executing statement: '%s'" % s)
@@ -402,30 +355,52 b' class IPControllerApp(ApplicationWithClusterDir):'
402 355 except:
403 356 self.log.msg("Error running statement: %s" % s)
404 357
405 def start_logging(self):
406 super(IPControllerApp, self).start_logging()
407 if self.master_config.Global.log_url:
408 context = self.factory.context
358 def forward_logging(self):
359 if self.log_url:
360 self.log.info("Forwarding logging to %s"%self.log_url)
361 context = zmq.Context.instance()
409 362 lsock = context.socket(zmq.PUB)
410 lsock.connect(self.master_config.Global.log_url)
363 lsock.connect(self.log_url)
411 364 handler = PUBHandler(lsock)
365 self.log.removeHandler(self._log_handler)
412 366 handler.root_topic = 'controller'
413 367 handler.setLevel(self.log_level)
414 368 self.log.addHandler(handler)
415 #
416 def start_app(self):
369 self._log_handler = handler
370 # #
371
372 def initialize(self, argv=None):
373 super(IPControllerApp, self).initialize(argv)
374 self.forward_logging()
375 self.init_hub()
376 self.init_schedulers()
377
378 def start(self):
417 379 # Start the subprocesses:
418 380 self.factory.start()
381 child_procs = []
382 for child in self.children:
383 child.start()
384 if isinstance(child, ProcessMonitoredQueue):
385 child_procs.append(child.launcher)
386 elif isinstance(child, Process):
387 child_procs.append(child)
388 if child_procs:
389 signal_children(child_procs)
390
419 391 self.write_pid_file(overwrite=True)
392
420 393 try:
421 394 self.factory.loop.start()
422 395 except KeyboardInterrupt:
423 396 self.log.critical("Interrupted, Exiting...\n")
397
424 398
425 399
426 400 def launch_new_instance():
427 401 """Create and run the IPython controller"""
428 app = IPControllerApp()
402 app = IPControllerApp.instance()
403 app.initialize()
429 404 app.start()
430 405
431 406
@@ -2,10 +2,16 b''
2 2 # encoding: utf-8
3 3 """
4 4 The IPython engine application
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
5 11 """
6 12
7 13 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
9 15 #
10 16 # Distributed under the terms of the BSD License. The full license is in
11 17 # the file COPYING, distributed as part of this software.
@@ -22,18 +28,18 b' import sys'
22 28 import zmq
23 29 from zmq.eventloop import ioloop
24 30
25 from IPython.parallel.apps.clusterdir import (
26 ApplicationWithClusterDir,
27 ClusterDirConfigLoader
28 )
31 from IPython.core.profiledir import ProfileDir
32 from IPython.parallel.apps.baseapp import BaseParallelApplication
29 33 from IPython.zmq.log import EnginePUBHandler
30 34
31 from IPython.parallel import factory
35 from IPython.config.configurable import Configurable
36 from IPython.zmq.session import Session
32 37 from IPython.parallel.engine.engine import EngineFactory
33 38 from IPython.parallel.engine.streamkernel import Kernel
34 39 from IPython.parallel.util import disambiguate_url
35 40
36 41 from IPython.utils.importstring import import_item
42 from IPython.utils.traitlets import Bool, Unicode, Dict, List
37 43
38 44
39 45 #-----------------------------------------------------------------------------
@@ -43,6 +49,20 b' from IPython.utils.importstring import import_item'
43 49 #: The default config file name for this application
44 50 default_config_file_name = u'ipengine_config.py'
45 51
52 _description = """Start an IPython engine for parallel computing.
53
54 IPython engines run in parallel and perform computations on behalf of a client
55 and controller. A controller needs to be started before the engines. The
56 engine can be configured using command line options or using a cluster
57 directory. Cluster directories contain config, log and security files and are
58 usually located in your ipython directory and named as "profile_name".
59 See the `profile` and `profile_dir` options for details.
60 """
61
62
63 #-----------------------------------------------------------------------------
64 # MPI configuration
65 #-----------------------------------------------------------------------------
46 66
47 67 mpi4py_init = """from mpi4py import MPI as mpi
48 68 mpi.size = mpi.COMM_WORLD.Get_size()
@@ -58,123 +78,78 b' mpi.rank = 0'
58 78 mpi.size = 0
59 79 """
60 80
81 class MPI(Configurable):
82 """Configurable for MPI initialization"""
83 use = Unicode('', config=True,
84 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
85 )
61 86
62 _description = """Start an IPython engine for parallel computing.\n\n
87 def _on_use_changed(self, old, new):
88 # load default init script if it's not set
89 if not self.init_script:
90 self.init_script = self.default_inits.get(new, '')
91
92 init_script = Unicode('', config=True,
93 help="Initialization code for MPI")
94
95 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
96 config=True)
63 97
64 IPython engines run in parallel and perform computations on behalf of a client
65 and controller. A controller needs to be started before the engines. The
66 engine can be configured using command line options or using a cluster
67 directory. Cluster directories contain config, log and security files and are
68 usually located in your ipython directory and named as "cluster_<profile>".
69 See the --profile and --cluster-dir options for details.
70 """
71 98
72 99 #-----------------------------------------------------------------------------
73 # Command line options
100 # Main application
74 101 #-----------------------------------------------------------------------------
75 102
76 103
77 class IPEngineAppConfigLoader(ClusterDirConfigLoader):
78
79 def _add_arguments(self):
80 super(IPEngineAppConfigLoader, self)._add_arguments()
81 paa = self.parser.add_argument
82 # Controller config
83 paa('--file', '-f',
84 type=unicode, dest='Global.url_file',
85 help='The full location of the file containing the connection information fo '
86 'controller. If this is not given, the file must be in the '
87 'security directory of the cluster directory. This location is '
88 'resolved using the --profile and --app-dir options.',
89 metavar='Global.url_file')
90 # MPI
91 paa('--mpi',
92 type=str, dest='MPI.use',
93 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
94 metavar='MPI.use')
95 # Global config
96 paa('--log-to-file',
97 action='store_true', dest='Global.log_to_file',
98 help='Log to a file in the log directory (default is stdout)')
99 paa('--log-url',
100 dest='Global.log_url',
101 help="url of ZMQ logger, as started with iploggerz")
102 # paa('--execkey',
103 # type=str, dest='Global.exec_key',
104 # help='path to a file containing an execution key.',
105 # metavar='keyfile')
106 # paa('--no-secure',
107 # action='store_false', dest='Global.secure',
108 # help='Turn off execution keys.')
109 # paa('--secure',
110 # action='store_true', dest='Global.secure',
111 # help='Turn on execution keys (default).')
112 # init command
113 paa('-c',
114 type=str, dest='Global.extra_exec_lines',
104 class IPEngineApp(BaseParallelApplication):
105
106 name = Unicode(u'ipengine')
107 description = Unicode(_description)
108 config_file_name = Unicode(default_config_file_name)
109 classes = List([ProfileDir, Session, EngineFactory, Kernel, MPI])
110
111 startup_script = Unicode(u'', config=True,
112 help='specify a script to be run at startup')
113 startup_command = Unicode('', config=True,
115 114 help='specify a command to be run at startup')
116 paa('-s',
117 type=unicode, dest='Global.extra_exec_file',
118 help='specify a script to be run at startup')
119
120 factory.add_session_arguments(self.parser)
121 factory.add_registration_arguments(self.parser)
122 115
116 url_file = Unicode(u'', config=True,
117 help="""The full location of the file containing the connection information for
118 the controller. If this is not given, the file must be in the
119 security directory of the cluster directory. This location is
120 resolved using the `profile` or `profile_dir` options.""",
121 )
123 122
124 #-----------------------------------------------------------------------------
125 # Main application
126 #-----------------------------------------------------------------------------
123 url_file_name = Unicode(u'ipcontroller-engine.json')
124 log_url = Unicode('', config=True,
125 help="""The URL for the iploggerapp instance, for forwarding
126 logging to a central location.""")
127 127
128 aliases = Dict(dict(
129 file = 'IPEngineApp.url_file',
130 c = 'IPEngineApp.startup_command',
131 s = 'IPEngineApp.startup_script',
128 132
129 class IPEngineApp(ApplicationWithClusterDir):
130
131 name = u'ipengine'
132 description = _description
133 command_line_loader = IPEngineAppConfigLoader
134 default_config_file_name = default_config_file_name
135 auto_create_cluster_dir = True
136
137 def create_default_config(self):
138 super(IPEngineApp, self).create_default_config()
139
140 # The engine should not clean logs as we don't want to remove the
141 # active log files of other running engines.
142 self.default_config.Global.clean_logs = False
143 self.default_config.Global.secure = True
144
145 # Global config attributes
146 self.default_config.Global.exec_lines = []
147 self.default_config.Global.extra_exec_lines = ''
148 self.default_config.Global.extra_exec_file = u''
149
150 # Configuration related to the controller
151 # This must match the filename (path not included) that the controller
152 # used for the FURL file.
153 self.default_config.Global.url_file = u''
154 self.default_config.Global.url_file_name = u'ipcontroller-engine.json'
155 # If given, this is the actual location of the controller's FURL file.
156 # If not, this is computed using the profile, app_dir and furl_file_name
157 # self.default_config.Global.key_file_name = u'exec_key.key'
158 # self.default_config.Global.key_file = u''
159
160 # MPI related config attributes
161 self.default_config.MPI.use = ''
162 self.default_config.MPI.mpi4py = mpi4py_init
163 self.default_config.MPI.pytrilinos = pytrilinos_init
164
165 def post_load_command_line_config(self):
166 pass
167
168 def pre_construct(self):
169 super(IPEngineApp, self).pre_construct()
170 # self.find_cont_url_file()
171 self.find_url_file()
172 if self.master_config.Global.extra_exec_lines:
173 self.master_config.Global.exec_lines.append(self.master_config.Global.extra_exec_lines)
174 if self.master_config.Global.extra_exec_file:
175 enc = sys.getfilesystemencoding() or 'utf8'
176 cmd="execfile(%r)"%self.master_config.Global.extra_exec_file.encode(enc)
177 self.master_config.Global.exec_lines.append(cmd)
133 ident = 'Session.session',
134 user = 'Session.username',
135 exec_key = 'Session.keyfile',
136
137 url = 'EngineFactory.url',
138 ip = 'EngineFactory.ip',
139 transport = 'EngineFactory.transport',
140 port = 'EngineFactory.regport',
141 location = 'EngineFactory.location',
142
143 timeout = 'EngineFactory.timeout',
144
145 profile = "IPEngineApp.profile",
146 profile_dir = 'ProfileDir.location',
147
148 mpi = 'MPI.use',
149
150 log_level = 'IPEngineApp.log_level',
151 log_url = 'IPEngineApp.log_url'
152 ))
178 153
179 154 # def find_key_file(self):
180 155 # """Set the key file.
@@ -186,7 +161,7 b' class IPEngineApp(ApplicationWithClusterDir):'
186 161 # # Find the actual controller key file
187 162 # if not config.Global.key_file:
188 163 # try_this = os.path.join(
189 # config.Global.cluster_dir,
164 # config.Global.profile_dir,
190 165 # config.Global.security_dir,
191 166 # config.Global.key_file_name
192 167 # )
@@ -198,82 +173,74 b' class IPEngineApp(ApplicationWithClusterDir):'
198 173 Here we don't try to actually see if it exists for is valid as that
199 174 is hadled by the connection logic.
200 175 """
201 config = self.master_config
176 config = self.config
202 177 # Find the actual controller key file
203 if not config.Global.url_file:
204 try_this = os.path.join(
205 config.Global.cluster_dir,
206 config.Global.security_dir,
207 config.Global.url_file_name
178 if not self.url_file:
179 self.url_file = os.path.join(
180 self.profile_dir.security_dir,
181 self.url_file_name
208 182 )
209 config.Global.url_file = try_this
210
211 def construct(self):
183 def init_engine(self):
212 184 # This is the working dir by now.
213 185 sys.path.insert(0, '')
214 config = self.master_config
186 config = self.config
187 # print config
188 self.find_url_file()
189
215 190 # if os.path.exists(config.Global.key_file) and config.Global.secure:
216 191 # config.SessionFactory.exec_key = config.Global.key_file
217 if os.path.exists(config.Global.url_file):
218 with open(config.Global.url_file) as f:
192 if os.path.exists(self.url_file):
193 with open(self.url_file) as f:
219 194 d = json.loads(f.read())
220 195 for k,v in d.iteritems():
221 196 if isinstance(v, unicode):
222 197 d[k] = v.encode()
223 198 if d['exec_key']:
224 config.SessionFactory.exec_key = d['exec_key']
199 config.Session.key = d['exec_key']
225 200 d['url'] = disambiguate_url(d['url'], d['location'])
226 config.RegistrationFactory.url=d['url']
201 config.EngineFactory.url = d['url']
227 202 config.EngineFactory.location = d['location']
228 203
204 try:
205 exec_lines = config.Kernel.exec_lines
206 except AttributeError:
207 config.Kernel.exec_lines = []
208 exec_lines = config.Kernel.exec_lines
229 209
230
231 config.Kernel.exec_lines = config.Global.exec_lines
232
233 self.start_mpi()
210 if self.startup_script:
211 enc = sys.getfilesystemencoding() or 'utf8'
212 cmd="execfile(%r)"%self.startup_script.encode(enc)
213 exec_lines.append(cmd)
214 if self.startup_command:
215 exec_lines.append(self.startup_command)
234 216
235 # Create the underlying shell class and EngineService
217 # Create the underlying shell class and Engine
236 218 # shell_class = import_item(self.master_config.Global.shell_class)
219 # print self.config
237 220 try:
238 self.engine = EngineFactory(config=config, logname=self.log.name)
221 self.engine = EngineFactory(config=config, log=self.log)
239 222 except:
240 223 self.log.error("Couldn't start the Engine", exc_info=True)
241 224 self.exit(1)
242 225
243 self.start_logging()
244
245 # Create the service hierarchy
246 # self.main_service = service.MultiService()
247 # self.engine_service.setServiceParent(self.main_service)
248 # self.tub_service = Tub()
249 # self.tub_service.setServiceParent(self.main_service)
250 # # This needs to be called before the connection is initiated
251 # self.main_service.startService()
252
253 # This initiates the connection to the controller and calls
254 # register_engine to tell the controller we are ready to do work
255 # self.engine_connector = EngineConnector(self.tub_service)
256
257 # self.log.info("Using furl file: %s" % self.master_config.Global.furl_file)
258
259 # reactor.callWhenRunning(self.call_connect)
260
261
262 def start_logging(self):
263 super(IPEngineApp, self).start_logging()
264 if self.master_config.Global.log_url:
226 def forward_logging(self):
227 if self.log_url:
228 self.log.info("Forwarding logging to %s"%self.log_url)
265 229 context = self.engine.context
266 230 lsock = context.socket(zmq.PUB)
267 lsock.connect(self.master_config.Global.log_url)
231 lsock.connect(self.log_url)
232 self.log.removeHandler(self._log_handler)
268 233 handler = EnginePUBHandler(self.engine, lsock)
269 234 handler.setLevel(self.log_level)
270 235 self.log.addHandler(handler)
271
272 def start_mpi(self):
236 self._log_handler = handler
237 #
238 def init_mpi(self):
273 239 global mpi
274 mpikey = self.master_config.MPI.use
275 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
276 if mpi_import_statement is not None:
240 self.mpi = MPI(config=self.config)
241
242 mpi_import_statement = self.mpi.init_script
243 if mpi_import_statement:
277 244 try:
278 245 self.log.info("Initializing MPI:")
279 246 self.log.info(mpi_import_statement)
@@ -283,8 +250,13 b' class IPEngineApp(ApplicationWithClusterDir):'
283 250 else:
284 251 mpi = None
285 252
286
287 def start_app(self):
253 def initialize(self, argv=None):
254 super(IPEngineApp, self).initialize(argv)
255 self.init_mpi()
256 self.init_engine()
257 self.forward_logging()
258
259 def start(self):
288 260 self.engine.start()
289 261 try:
290 262 self.engine.loop.start()
@@ -293,8 +265,9 b' class IPEngineApp(ApplicationWithClusterDir):'
293 265
294 266
295 267 def launch_new_instance():
296 """Create and run the IPython controller"""
297 app = IPEngineApp()
268 """Create and run the IPython engine"""
269 app = IPEngineApp.instance()
270 app.initialize()
298 271 app.start()
299 272
300 273
@@ -2,6 +2,11 b''
2 2 # encoding: utf-8
3 3 """
4 4 A simple IPython logger application
5
6 Authors:
7
8 * MinRK
9
5 10 """
6 11
7 12 #-----------------------------------------------------------------------------
@@ -20,9 +25,12 b' import sys'
20 25
21 26 import zmq
22 27
23 from IPython.parallel.apps.clusterdir import (
24 ApplicationWithClusterDir,
25 ClusterDirConfigLoader
28 from IPython.core.profiledir import ProfileDir
29 from IPython.utils.traitlets import Bool, Dict, Unicode
30
31 from IPython.parallel.apps.baseapp import (
32 BaseParallelApplication,
33 base_aliases
26 34 )
27 35 from IPython.parallel.apps.logwatcher import LogWatcher
28 36
@@ -33,89 +41,49 b' from IPython.parallel.apps.logwatcher import LogWatcher'
33 41 #: The default config file name for this application
34 42 default_config_file_name = u'iplogger_config.py'
35 43
36 _description = """Start an IPython logger for parallel computing.\n\n
44 _description = """Start an IPython logger for parallel computing.
37 45
38 46 IPython controllers and engines (and your own processes) can broadcast log messages
39 47 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
40 48 logger can be configured using command line options or using a cluster
41 49 directory. Cluster directories contain config, log and security files and are
42 usually located in your ipython directory and named as "cluster_<profile>".
43 See the --profile and --cluster-dir options for details.
50 usually located in your ipython directory and named as "profile_name".
51 See the `profile` and `profile_dir` options for details.
44 52 """
45 53
46 #-----------------------------------------------------------------------------
47 # Command line options
48 #-----------------------------------------------------------------------------
49
50
51 class IPLoggerAppConfigLoader(ClusterDirConfigLoader):
52
53 def _add_arguments(self):
54 super(IPLoggerAppConfigLoader, self)._add_arguments()
55 paa = self.parser.add_argument
56 # Controller config
57 paa('--url',
58 type=str, dest='LogWatcher.url',
59 help='The url the LogWatcher will listen on',
60 )
61 # MPI
62 paa('--topics',
63 type=str, dest='LogWatcher.topics', nargs='+',
64 help='What topics to subscribe to',
65 metavar='topics')
66 # Global config
67 paa('--log-to-file',
68 action='store_true', dest='Global.log_to_file',
69 help='Log to a file in the log directory (default is stdout)')
70
71 54
72 55 #-----------------------------------------------------------------------------
73 56 # Main application
74 57 #-----------------------------------------------------------------------------
58 aliases = {}
59 aliases.update(base_aliases)
60 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
75 61
62 class IPLoggerApp(BaseParallelApplication):
76 63
77 class IPLoggerApp(ApplicationWithClusterDir):
78
79 name = u'iploggerz'
64 name = u'iplogger'
80 65 description = _description
81 command_line_loader = IPLoggerAppConfigLoader
82 default_config_file_name = default_config_file_name
83 auto_create_cluster_dir = True
84
85 def create_default_config(self):
86 super(IPLoggerApp, self).create_default_config()
87
88 # The engine should not clean logs as we don't want to remove the
89 # active log files of other running engines.
90 self.default_config.Global.clean_logs = False
91
92 # If given, this is the actual location of the logger's URL file.
93 # If not, this is computed using the profile, app_dir and furl_file_name
94 self.default_config.Global.url_file_name = u'iplogger.url'
95 self.default_config.Global.url_file = u''
96
97 def post_load_command_line_config(self):
98 pass
99
100 def pre_construct(self):
101 super(IPLoggerApp, self).pre_construct()
102
103 def construct(self):
104 # This is the working dir by now.
105 sys.path.insert(0, '')
106
107 self.start_logging()
108
66 config_file_name = Unicode(default_config_file_name)
67
68 classes = [LogWatcher, ProfileDir]
69 aliases = Dict(aliases)
70
71 def initialize(self, argv=None):
72 super(IPLoggerApp, self).initialize(argv)
73 self.init_watcher()
74
75 def init_watcher(self):
109 76 try:
110 self.watcher = LogWatcher(config=self.master_config, logname=self.log.name)
77 self.watcher = LogWatcher(config=self.config, log=self.log)
111 78 except:
112 79 self.log.error("Couldn't start the LogWatcher", exc_info=True)
113 80 self.exit(1)
81 self.log.info("Listening for log messages on %r"%self.watcher.url)
114 82
115 83
116 def start_app(self):
84 def start(self):
85 self.watcher.start()
117 86 try:
118 self.watcher.start()
119 87 self.watcher.loop.start()
120 88 except KeyboardInterrupt:
121 89 self.log.critical("Logging Interrupted, shutting down...\n")
@@ -123,7 +91,8 b' class IPLoggerApp(ApplicationWithClusterDir):'
123 91
124 92 def launch_new_instance():
125 93 """Create and run the IPython LogWatcher"""
126 app = IPLoggerApp()
94 app = IPLoggerApp.instance()
95 app.initialize()
127 96 app.start()
128 97
129 98
@@ -2,10 +2,15 b''
2 2 # encoding: utf-8
3 3 """
4 4 Facilities for launching IPython processes asynchronously.
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
5 10 """
6 11
7 12 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
9 14 #
10 15 # Distributed under the terms of the BSD License. The full license is in
11 16 # the file COPYING, distributed as part of this software.
@@ -49,14 +54,13 b' except ImportError:'
49 54
50 55 from zmq.eventloop import ioloop
51 56
52 from IPython.external import Itpl
53 # from IPython.config.configurable import Configurable
54 from IPython.utils.traitlets import Any, Str, Int, List, Unicode, Dict, Instance, CUnicode
57 from IPython.config.application import Application
58 from IPython.config.configurable import LoggingConfigurable
59 from IPython.utils.text import EvalFormatter
60 from IPython.utils.traitlets import Any, Int, List, Unicode, Dict, Instance
55 61 from IPython.utils.path import get_ipython_module_path
56 62 from IPython.utils.process import find_cmd, pycmd2argv, FindCmdError
57 63
58 from IPython.parallel.factory import LoggingFactory
59
60 64 from .win32support import forward_read_events
61 65
62 66 from .winhpcjob import IPControllerTask, IPEngineTask, IPControllerJob, IPEngineSetJob
@@ -97,16 +101,16 b' class UnknownStatus(LauncherError):'
97 101 pass
98 102
99 103
100 class BaseLauncher(LoggingFactory):
104 class BaseLauncher(LoggingConfigurable):
101 105 """An asbtraction for starting, stopping and signaling a process."""
102 106
103 107 # In all of the launchers, the work_dir is where child processes will be
104 # run. This will usually be the cluster_dir, but may not be. any work_dir
108 # run. This will usually be the profile_dir, but may not be. any work_dir
105 109 # passed into the __init__ method will override the config value.
106 110 # This should not be used to set the work_dir for the actual engine
107 111 # and controller. Instead, use their own config files or the
108 112 # controller_args, engine_args attributes of the launchers to add
109 # the --work-dir option.
113 # the work_dir option.
110 114 work_dir = Unicode(u'.')
111 115 loop = Instance('zmq.eventloop.ioloop.IOLoop')
112 116
@@ -153,29 +157,20 b' class BaseLauncher(LoggingFactory):'
153 157 return False
154 158
155 159 def start(self):
156 """Start the process.
157
158 This must return a deferred that fires with information about the
159 process starting (like a pid, job id, etc.).
160 """
160 """Start the process."""
161 161 raise NotImplementedError('start must be implemented in a subclass')
162 162
163 163 def stop(self):
164 164 """Stop the process and notify observers of stopping.
165 165
166 This must return a deferred that fires with information about the
167 processing stopping, like errors that occur while the process is
168 attempting to be shut down. This deferred won't fire when the process
169 actually stops. To observe the actual process stopping, see
170 :func:`observe_stop`.
166 This method will return None immediately.
167 To observe the actual process stopping, see :meth:`on_stop`.
171 168 """
172 169 raise NotImplementedError('stop must be implemented in a subclass')
173 170
174 171 def on_stop(self, f):
175 """Get a deferred that will fire when the process stops.
176
177 The deferred will fire with data that contains information about
178 the exit status of the process.
172 """Register a callback to be called with this Launcher's stop_data
173 when the process actually finishes.
179 174 """
180 175 if self.state=='after':
181 176 return f(self.stop_data)
@@ -198,7 +193,7 b' class BaseLauncher(LoggingFactory):'
198 193 """Call this to trigger process stop actions.
199 194
200 195 This logs the process stopping and sets the state to 'after'. Call
201 this to trigger all the deferreds from :func:`observe_stop`."""
196 this to trigger callbacks registered via :meth:`on_stop`."""
202 197
203 198 self.log.info('Process %r stopped: %r' % (self.args[0], data))
204 199 self.stop_data = data
@@ -211,8 +206,6 b' class BaseLauncher(LoggingFactory):'
211 206 def signal(self, sig):
212 207 """Signal the process.
213 208
214 Return a semi-meaningless deferred after signaling the process.
215
216 209 Parameters
217 210 ----------
218 211 sig : str or int
@@ -243,7 +236,6 b' class LocalProcessLauncher(BaseLauncher):'
243 236 work_dir=work_dir, config=config, **kwargs
244 237 )
245 238 self.process = None
246 self.start_deferred = None
247 239 self.poller = None
248 240
249 241 def find_args(self):
@@ -328,17 +320,19 b' class LocalProcessLauncher(BaseLauncher):'
328 320 class LocalControllerLauncher(LocalProcessLauncher):
329 321 """Launch a controller as a regular external process."""
330 322
331 controller_cmd = List(ipcontroller_cmd_argv, config=True)
323 controller_cmd = List(ipcontroller_cmd_argv, config=True,
324 help="""Popen command to launch ipcontroller.""")
332 325 # Command line arguments to ipcontroller.
333 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
326 controller_args = List(['--log-to-file','log_level=%i'%logging.INFO], config=True,
327 help="""command-line args to pass to ipcontroller""")
334 328
335 329 def find_args(self):
336 330 return self.controller_cmd + self.controller_args
337 331
338 def start(self, cluster_dir):
339 """Start the controller by cluster_dir."""
340 self.controller_args.extend(['--cluster-dir', cluster_dir])
341 self.cluster_dir = unicode(cluster_dir)
332 def start(self, profile_dir):
333 """Start the controller by profile_dir."""
334 self.controller_args.extend(['profile_dir=%s'%profile_dir])
335 self.profile_dir = unicode(profile_dir)
342 336 self.log.info("Starting LocalControllerLauncher: %r" % self.args)
343 337 return super(LocalControllerLauncher, self).start()
344 338
@@ -346,19 +340,20 b' class LocalControllerLauncher(LocalProcessLauncher):'
346 340 class LocalEngineLauncher(LocalProcessLauncher):
347 341 """Launch a single engine as a regular externall process."""
348 342
349 engine_cmd = List(ipengine_cmd_argv, config=True)
343 engine_cmd = List(ipengine_cmd_argv, config=True,
344 help="""command to launch the Engine.""")
350 345 # Command line arguments for ipengine.
351 engine_args = List(
352 ['--log-to-file','--log-level', str(logging.INFO)], config=True
346 engine_args = List(['--log-to-file','log_level=%i'%logging.INFO], config=True,
347 help="command-line arguments to pass to ipengine"
353 348 )
354 349
355 350 def find_args(self):
356 351 return self.engine_cmd + self.engine_args
357 352
358 def start(self, cluster_dir):
359 """Start the engine by cluster_dir."""
360 self.engine_args.extend(['--cluster-dir', cluster_dir])
361 self.cluster_dir = unicode(cluster_dir)
353 def start(self, profile_dir):
354 """Start the engine by profile_dir."""
355 self.engine_args.extend(['profile_dir=%s'%profile_dir])
356 self.profile_dir = unicode(profile_dir)
362 357 return super(LocalEngineLauncher, self).start()
363 358
364 359
@@ -367,7 +362,8 b' class LocalEngineSetLauncher(BaseLauncher):'
367 362
368 363 # Command line arguments for ipengine.
369 364 engine_args = List(
370 ['--log-to-file','--log-level', str(logging.INFO)], config=True
365 ['--log-to-file','log_level=%i'%logging.INFO], config=True,
366 help="command-line arguments to pass to ipengine"
371 367 )
372 368 # launcher class
373 369 launcher_class = LocalEngineLauncher
@@ -381,16 +377,16 b' class LocalEngineSetLauncher(BaseLauncher):'
381 377 )
382 378 self.stop_data = {}
383 379
384 def start(self, n, cluster_dir):
385 """Start n engines by profile or cluster_dir."""
386 self.cluster_dir = unicode(cluster_dir)
380 def start(self, n, profile_dir):
381 """Start n engines by profile or profile_dir."""
382 self.profile_dir = unicode(profile_dir)
387 383 dlist = []
388 384 for i in range(n):
389 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
385 el = self.launcher_class(work_dir=self.work_dir, config=self.config, log=self.log)
390 386 # Copy the engine args over to each engine launcher.
391 387 el.engine_args = copy.deepcopy(self.engine_args)
392 388 el.on_stop(self._notice_engine_stopped)
393 d = el.start(cluster_dir)
389 d = el.start(profile_dir)
394 390 if i==0:
395 391 self.log.info("Starting LocalEngineSetLauncher: %r" % el.args)
396 392 self.launchers[i] = el
@@ -442,16 +438,18 b' class LocalEngineSetLauncher(BaseLauncher):'
442 438 class MPIExecLauncher(LocalProcessLauncher):
443 439 """Launch an external process using mpiexec."""
444 440
445 # The mpiexec command to use in starting the process.
446 mpi_cmd = List(['mpiexec'], config=True)
447 # The command line arguments to pass to mpiexec.
448 mpi_args = List([], config=True)
449 # The program to start using mpiexec.
450 program = List(['date'], config=True)
451 # The command line argument to the program.
452 program_args = List([], config=True)
453 # The number of instances of the program to start.
454 n = Int(1, config=True)
441 mpi_cmd = List(['mpiexec'], config=True,
442 help="The mpiexec command to use in starting the process."
443 )
444 mpi_args = List([], config=True,
445 help="The command line arguments to pass to mpiexec."
446 )
447 program = List(['date'], config=True,
448 help="The program to start via mpiexec.")
449 program_args = List([], config=True,
450 help="The command line argument to the program."
451 )
452 n = Int(1)
455 453
456 454 def find_args(self):
457 455 """Build self.args using all the fields."""
@@ -467,15 +465,18 b' class MPIExecLauncher(LocalProcessLauncher):'
467 465 class MPIExecControllerLauncher(MPIExecLauncher):
468 466 """Launch a controller using mpiexec."""
469 467
470 controller_cmd = List(ipcontroller_cmd_argv, config=True)
471 # Command line arguments to ipcontroller.
472 controller_args = List(['--log-to-file','--log-level', str(logging.INFO)], config=True)
473 n = Int(1, config=False)
468 controller_cmd = List(ipcontroller_cmd_argv, config=True,
469 help="Popen command to launch the Contropper"
470 )
471 controller_args = List(['--log-to-file','log_level=%i'%logging.INFO], config=True,
472 help="Command line arguments to pass to ipcontroller."
473 )
474 n = Int(1)
474 475
475 def start(self, cluster_dir):
476 """Start the controller by cluster_dir."""
477 self.controller_args.extend(['--cluster-dir', cluster_dir])
478 self.cluster_dir = unicode(cluster_dir)
476 def start(self, profile_dir):
477 """Start the controller by profile_dir."""
478 self.controller_args.extend(['profile_dir=%s'%profile_dir])
479 self.profile_dir = unicode(profile_dir)
479 480 self.log.info("Starting MPIExecControllerLauncher: %r" % self.args)
480 481 return super(MPIExecControllerLauncher, self).start(1)
481 482
@@ -486,17 +487,19 b' class MPIExecControllerLauncher(MPIExecLauncher):'
486 487
487 488 class MPIExecEngineSetLauncher(MPIExecLauncher):
488 489
489 program = List(ipengine_cmd_argv, config=True)
490 # Command line arguments for ipengine.
490 program = List(ipengine_cmd_argv, config=True,
491 help="Popen command for ipengine"
492 )
491 493 program_args = List(
492 ['--log-to-file','--log-level', str(logging.INFO)], config=True
494 ['--log-to-file','log_level=%i'%logging.INFO], config=True,
495 help="Command line arguments for ipengine."
493 496 )
494 n = Int(1, config=True)
497 n = Int(1)
495 498
496 def start(self, n, cluster_dir):
497 """Start n engines by profile or cluster_dir."""
498 self.program_args.extend(['--cluster-dir', cluster_dir])
499 self.cluster_dir = unicode(cluster_dir)
499 def start(self, n, profile_dir):
500 """Start n engines by profile or profile_dir."""
501 self.program_args.extend(['profile_dir=%s'%profile_dir])
502 self.profile_dir = unicode(profile_dir)
500 503 self.n = n
501 504 self.log.info('Starting MPIExecEngineSetLauncher: %r' % self.args)
502 505 return super(MPIExecEngineSetLauncher, self).start(n)
@@ -505,7 +508,7 b' class MPIExecEngineSetLauncher(MPIExecLauncher):'
505 508 # SSH launchers
506 509 #-----------------------------------------------------------------------------
507 510
508 # TODO: Get SSH Launcher working again.
511 # TODO: Get SSH Launcher back to level of sshx in 0.10.2
509 512
510 513 class SSHLauncher(LocalProcessLauncher):
511 514 """A minimal launcher for ssh.
@@ -515,13 +518,20 b' class SSHLauncher(LocalProcessLauncher):'
515 518 as well.
516 519 """
517 520
518 ssh_cmd = List(['ssh'], config=True)
519 ssh_args = List(['-tt'], config=True)
520 program = List(['date'], config=True)
521 program_args = List([], config=True)
522 hostname = CUnicode('', config=True)
523 user = CUnicode('', config=True)
524 location = CUnicode('')
521 ssh_cmd = List(['ssh'], config=True,
522 help="command for starting ssh")
523 ssh_args = List(['-tt'], config=True,
524 help="args to pass to ssh")
525 program = List(['date'], config=True,
526 help="Program to launch via ssh")
527 program_args = List([], config=True,
528 help="args to pass to remote program")
529 hostname = Unicode('', config=True,
530 help="hostname on which to launch the program")
531 user = Unicode('', config=True,
532 help="username for ssh")
533 location = Unicode('', config=True,
534 help="user@hostname location for ssh in one setting")
525 535
526 536 def _hostname_changed(self, name, old, new):
527 537 if self.user:
@@ -536,8 +546,8 b' class SSHLauncher(LocalProcessLauncher):'
536 546 return self.ssh_cmd + self.ssh_args + [self.location] + \
537 547 self.program + self.program_args
538 548
539 def start(self, cluster_dir, hostname=None, user=None):
540 self.cluster_dir = unicode(cluster_dir)
549 def start(self, profile_dir, hostname=None, user=None):
550 self.profile_dir = unicode(profile_dir)
541 551 if hostname is not None:
542 552 self.hostname = hostname
543 553 if user is not None:
@@ -555,28 +565,33 b' class SSHLauncher(LocalProcessLauncher):'
555 565
556 566 class SSHControllerLauncher(SSHLauncher):
557 567
558 program = List(ipcontroller_cmd_argv, config=True)
559 # Command line arguments to ipcontroller.
560 program_args = List(['-r', '--log-to-file','--log-level', str(logging.INFO)], config=True)
568 program = List(ipcontroller_cmd_argv, config=True,
569 help="remote ipcontroller command.")
570 program_args = List(['--reuse-files', '--log-to-file','log_level=%i'%logging.INFO], config=True,
571 help="Command line arguments to ipcontroller.")
561 572
562 573
563 574 class SSHEngineLauncher(SSHLauncher):
564 program = List(ipengine_cmd_argv, config=True)
575 program = List(ipengine_cmd_argv, config=True,
576 help="remote ipengine command.")
565 577 # Command line arguments for ipengine.
566 578 program_args = List(
567 ['--log-to-file','--log-level', str(logging.INFO)], config=True
579 ['--log-to-file','log_level=%i'%logging.INFO], config=True,
580 help="Command line arguments to ipengine."
568 581 )
569 582
570 583 class SSHEngineSetLauncher(LocalEngineSetLauncher):
571 584 launcher_class = SSHEngineLauncher
572 engines = Dict(config=True)
585 engines = Dict(config=True,
586 help="""dict of engines to launch. This is a dict by hostname of ints,
587 corresponding to the number of engines to start on that host.""")
573 588
574 def start(self, n, cluster_dir):
575 """Start engines by profile or cluster_dir.
589 def start(self, n, profile_dir):
590 """Start engines by profile or profile_dir.
576 591 `n` is ignored, and the `engines` config property is used instead.
577 592 """
578 593
579 self.cluster_dir = unicode(cluster_dir)
594 self.profile_dir = unicode(profile_dir)
580 595 dlist = []
581 596 for host, n in self.engines.iteritems():
582 597 if isinstance(n, (tuple, list)):
@@ -589,13 +604,13 b' class SSHEngineSetLauncher(LocalEngineSetLauncher):'
589 604 else:
590 605 user=None
591 606 for i in range(n):
592 el = self.launcher_class(work_dir=self.work_dir, config=self.config, logname=self.log.name)
607 el = self.launcher_class(work_dir=self.work_dir, config=self.config, log=self.log)
593 608
594 609 # Copy the engine args over to each engine launcher.
595 610 i
596 611 el.program_args = args
597 612 el.on_stop(self._notice_engine_stopped)
598 d = el.start(cluster_dir, user=user, hostname=host)
613 d = el.start(profile_dir, user=user, hostname=host)
599 614 if i==0:
600 615 self.log.info("Starting SSHEngineSetLauncher: %r" % el.args)
601 616 self.launchers[host+str(i)] = el
@@ -624,17 +639,19 b' def find_job_cmd():'
624 639
625 640 class WindowsHPCLauncher(BaseLauncher):
626 641
627 # A regular expression used to get the job id from the output of the
628 # submit_command.
629 job_id_regexp = Str(r'\d+', config=True)
630 # The filename of the instantiated job script.
631 job_file_name = CUnicode(u'ipython_job.xml', config=True)
642 job_id_regexp = Unicode(r'\d+', config=True,
643 help="""A regular expression used to get the job id from the output of the
644 submit_command. """
645 )
646 job_file_name = Unicode(u'ipython_job.xml', config=True,
647 help="The filename of the instantiated job script.")
632 648 # The full path to the instantiated job script. This gets made dynamically
633 649 # by combining the work_dir with the job_file_name.
634 job_file = CUnicode(u'')
635 # The hostname of the scheduler to submit the job to
636 scheduler = CUnicode('', config=True)
637 job_cmd = CUnicode(find_job_cmd(), config=True)
650 job_file = Unicode(u'')
651 scheduler = Unicode('', config=True,
652 help="The hostname of the scheduler to submit the job to.")
653 job_cmd = Unicode(find_job_cmd(), config=True,
654 help="The command for submitting jobs.")
638 655
639 656 def __init__(self, work_dir=u'.', config=None, **kwargs):
640 657 super(WindowsHPCLauncher, self).__init__(
@@ -671,7 +688,7 b' class WindowsHPCLauncher(BaseLauncher):'
671 688 '/scheduler:%s' % self.scheduler
672 689 ]
673 690 self.log.info("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
674 # Twisted will raise DeprecationWarnings if we try to pass unicode to this
691
675 692 output = check_output([self.job_cmd]+args,
676 693 env=os.environ,
677 694 cwd=self.work_dir,
@@ -702,8 +719,10 b' class WindowsHPCLauncher(BaseLauncher):'
702 719
703 720 class WindowsHPCControllerLauncher(WindowsHPCLauncher):
704 721
705 job_file_name = CUnicode(u'ipcontroller_job.xml', config=True)
706 extra_args = List([], config=False)
722 job_file_name = Unicode(u'ipcontroller_job.xml', config=True,
723 help="WinHPC xml job file.")
724 extra_args = List([], config=False,
725 help="extra args to pass to ipcontroller")
707 726
708 727 def write_job_file(self, n):
709 728 job = IPControllerJob(config=self.config)
@@ -712,8 +731,8 b' class WindowsHPCControllerLauncher(WindowsHPCLauncher):'
712 731 # The tasks work directory is *not* the actual work directory of
713 732 # the controller. It is used as the base path for the stdout/stderr
714 733 # files that the scheduler redirects to.
715 t.work_directory = self.cluster_dir
716 # Add the --cluster-dir and from self.start().
734 t.work_directory = self.profile_dir
735 # Add the profile_dir and from self.start().
717 736 t.controller_args.extend(self.extra_args)
718 737 job.add_task(t)
719 738
@@ -722,19 +741,21 b' class WindowsHPCControllerLauncher(WindowsHPCLauncher):'
722 741
723 742 @property
724 743 def job_file(self):
725 return os.path.join(self.cluster_dir, self.job_file_name)
744 return os.path.join(self.profile_dir, self.job_file_name)
726 745
727 def start(self, cluster_dir):
728 """Start the controller by cluster_dir."""
729 self.extra_args = ['--cluster-dir', cluster_dir]
730 self.cluster_dir = unicode(cluster_dir)
746 def start(self, profile_dir):
747 """Start the controller by profile_dir."""
748 self.extra_args = ['profile_dir=%s'%profile_dir]
749 self.profile_dir = unicode(profile_dir)
731 750 return super(WindowsHPCControllerLauncher, self).start(1)
732 751
733 752
734 753 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):
735 754
736 job_file_name = CUnicode(u'ipengineset_job.xml', config=True)
737 extra_args = List([], config=False)
755 job_file_name = Unicode(u'ipengineset_job.xml', config=True,
756 help="jobfile for ipengines job")
757 extra_args = List([], config=False,
758 help="extra args to pas to ipengine")
738 759
739 760 def write_job_file(self, n):
740 761 job = IPEngineSetJob(config=self.config)
@@ -744,8 +765,8 b' class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):'
744 765 # The tasks work directory is *not* the actual work directory of
745 766 # the engine. It is used as the base path for the stdout/stderr
746 767 # files that the scheduler redirects to.
747 t.work_directory = self.cluster_dir
748 # Add the --cluster-dir and from self.start().
768 t.work_directory = self.profile_dir
769 # Add the profile_dir and from self.start().
749 770 t.engine_args.extend(self.extra_args)
750 771 job.add_task(t)
751 772
@@ -754,12 +775,12 b' class WindowsHPCEngineSetLauncher(WindowsHPCLauncher):'
754 775
755 776 @property
756 777 def job_file(self):
757 return os.path.join(self.cluster_dir, self.job_file_name)
778 return os.path.join(self.profile_dir, self.job_file_name)
758 779
759 def start(self, n, cluster_dir):
760 """Start the controller by cluster_dir."""
761 self.extra_args = ['--cluster-dir', cluster_dir]
762 self.cluster_dir = unicode(cluster_dir)
780 def start(self, n, profile_dir):
781 """Start the controller by profile_dir."""
782 self.extra_args = ['profile_dir=%s'%profile_dir]
783 self.profile_dir = unicode(profile_dir)
763 784 return super(WindowsHPCEngineSetLauncher, self).start(n)
764 785
765 786
@@ -776,41 +797,43 b' class BatchSystemLauncher(BaseLauncher):'
776 797
777 798 This class also has the notion of a batch script. The ``batch_template``
778 799 attribute can be set to a string that is a template for the batch script.
779 This template is instantiated using Itpl. Thus the template can use
780 ${n} fot the number of instances. Subclasses can add additional variables
800 This template is instantiated using string formatting. Thus the template can
801 use {n} fot the number of instances. Subclasses can add additional variables
781 802 to the template dict.
782 803 """
783 804
784 805 # Subclasses must fill these in. See PBSEngineSet
785 # The name of the command line program used to submit jobs.
786 submit_command = List([''], config=True)
787 # The name of the command line program used to delete jobs.
788 delete_command = List([''], config=True)
789 # A regular expression used to get the job id from the output of the
790 # submit_command.
791 job_id_regexp = CUnicode('', config=True)
792 # The string that is the batch script template itself.
793 batch_template = CUnicode('', config=True)
794 # The file that contains the batch template
795 batch_template_file = CUnicode(u'', config=True)
796 # The filename of the instantiated batch script.
797 batch_file_name = CUnicode(u'batch_script', config=True)
798 # The PBS Queue
799 queue = CUnicode(u'', config=True)
806 submit_command = List([''], config=True,
807 help="The name of the command line program used to submit jobs.")
808 delete_command = List([''], config=True,
809 help="The name of the command line program used to delete jobs.")
810 job_id_regexp = Unicode('', config=True,
811 help="""A regular expression used to get the job id from the output of the
812 submit_command.""")
813 batch_template = Unicode('', config=True,
814 help="The string that is the batch script template itself.")
815 batch_template_file = Unicode(u'', config=True,
816 help="The file that contains the batch template.")
817 batch_file_name = Unicode(u'batch_script', config=True,
818 help="The filename of the instantiated batch script.")
819 queue = Unicode(u'', config=True,
820 help="The PBS Queue.")
800 821
801 822 # not configurable, override in subclasses
802 823 # PBS Job Array regex
803 job_array_regexp = CUnicode('')
804 job_array_template = CUnicode('')
824 job_array_regexp = Unicode('')
825 job_array_template = Unicode('')
805 826 # PBS Queue regex
806 queue_regexp = CUnicode('')
807 queue_template = CUnicode('')
827 queue_regexp = Unicode('')
828 queue_template = Unicode('')
808 829 # The default batch template, override in subclasses
809 default_template = CUnicode('')
830 default_template = Unicode('')
810 831 # The full path to the instantiated batch script.
811 batch_file = CUnicode(u'')
832 batch_file = Unicode(u'')
812 833 # the format dict used with batch_template:
813 834 context = Dict()
835 # the Formatter instance for rendering the templates:
836 formatter = Instance(EvalFormatter, (), {})
814 837
815 838
816 839 def find_args(self):
@@ -837,7 +860,6 b' class BatchSystemLauncher(BaseLauncher):'
837 860 """Instantiate and write the batch script to the work_dir."""
838 861 self.context['n'] = n
839 862 self.context['queue'] = self.queue
840 print self.context
841 863 # first priority is batch_template if set
842 864 if self.batch_template_file and not self.batch_template:
843 865 # second priority is batch_template_file
@@ -861,20 +883,19 b' class BatchSystemLauncher(BaseLauncher):'
861 883 firstline, rest = self.batch_template.split('\n',1)
862 884 self.batch_template = u'\n'.join([firstline, self.queue_template, rest])
863 885
864 script_as_string = Itpl.itplns(self.batch_template, self.context)
886 script_as_string = self.formatter.format(self.batch_template, **self.context)
865 887 self.log.info('Writing instantiated batch script: %s' % self.batch_file)
866 888
867 889 with open(self.batch_file, 'w') as f:
868 890 f.write(script_as_string)
869 891 os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
870 892
871 def start(self, n, cluster_dir):
893 def start(self, n, profile_dir):
872 894 """Start n copies of the process using a batch system."""
873 # Here we save profile and cluster_dir in the context so they
874 # can be used in the batch script template as ${profile} and
875 # ${cluster_dir}
876 self.context['cluster_dir'] = cluster_dir
877 self.cluster_dir = unicode(cluster_dir)
895 # Here we save profile_dir in the context so they
896 # can be used in the batch script template as {profile_dir}
897 self.context['profile_dir'] = profile_dir
898 self.profile_dir = unicode(profile_dir)
878 899 self.write_batch_script(n)
879 900 output = check_output(self.args, env=os.environ)
880 901
@@ -891,84 +912,91 b' class BatchSystemLauncher(BaseLauncher):'
891 912 class PBSLauncher(BatchSystemLauncher):
892 913 """A BatchSystemLauncher subclass for PBS."""
893 914
894 submit_command = List(['qsub'], config=True)
895 delete_command = List(['qdel'], config=True)
896 job_id_regexp = CUnicode(r'\d+', config=True)
915 submit_command = List(['qsub'], config=True,
916 help="The PBS submit command ['qsub']")
917 delete_command = List(['qdel'], config=True,
918 help="The PBS delete command ['qsub']")
919 job_id_regexp = Unicode(r'\d+', config=True,
920 help="Regular expresion for identifying the job ID [r'\d+']")
897 921
898 batch_file = CUnicode(u'')
899 job_array_regexp = CUnicode('#PBS\W+-t\W+[\w\d\-\$]+')
900 job_array_template = CUnicode('#PBS -t 1-$n')
901 queue_regexp = CUnicode('#PBS\W+-q\W+\$?\w+')
902 queue_template = CUnicode('#PBS -q $queue')
922 batch_file = Unicode(u'')
923 job_array_regexp = Unicode('#PBS\W+-t\W+[\w\d\-\$]+')
924 job_array_template = Unicode('#PBS -t 1-{n}')
925 queue_regexp = Unicode('#PBS\W+-q\W+\$?\w+')
926 queue_template = Unicode('#PBS -q {queue}')
903 927
904 928
905 929 class PBSControllerLauncher(PBSLauncher):
906 930 """Launch a controller using PBS."""
907 931
908 batch_file_name = CUnicode(u'pbs_controller', config=True)
909 default_template= CUnicode("""#!/bin/sh
932 batch_file_name = Unicode(u'pbs_controller', config=True,
933 help="batch file name for the controller job.")
934 default_template= Unicode("""#!/bin/sh
910 935 #PBS -V
911 936 #PBS -N ipcontroller
912 %s --log-to-file --cluster-dir $cluster_dir
937 %s --log-to-file profile_dir={profile_dir}
913 938 """%(' '.join(ipcontroller_cmd_argv)))
914 939
915 def start(self, cluster_dir):
916 """Start the controller by profile or cluster_dir."""
940 def start(self, profile_dir):
941 """Start the controller by profile or profile_dir."""
917 942 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
918 return super(PBSControllerLauncher, self).start(1, cluster_dir)
943 return super(PBSControllerLauncher, self).start(1, profile_dir)
919 944
920 945
921 946 class PBSEngineSetLauncher(PBSLauncher):
922 947 """Launch Engines using PBS"""
923 batch_file_name = CUnicode(u'pbs_engines', config=True)
924 default_template= CUnicode(u"""#!/bin/sh
948 batch_file_name = Unicode(u'pbs_engines', config=True,
949 help="batch file name for the engine(s) job.")
950 default_template= Unicode(u"""#!/bin/sh
925 951 #PBS -V
926 952 #PBS -N ipengine
927 %s --cluster-dir $cluster_dir
953 %s profile_dir={profile_dir}
928 954 """%(' '.join(ipengine_cmd_argv)))
929 955
930 def start(self, n, cluster_dir):
931 """Start n engines by profile or cluster_dir."""
956 def start(self, n, profile_dir):
957 """Start n engines by profile or profile_dir."""
932 958 self.log.info('Starting %i engines with PBSEngineSetLauncher: %r' % (n, self.args))
933 return super(PBSEngineSetLauncher, self).start(n, cluster_dir)
959 return super(PBSEngineSetLauncher, self).start(n, profile_dir)
934 960
935 961 #SGE is very similar to PBS
936 962
937 963 class SGELauncher(PBSLauncher):
938 964 """Sun GridEngine is a PBS clone with slightly different syntax"""
939 job_array_regexp = CUnicode('#$$\W+-t\W+[\w\d\-\$]+')
940 job_array_template = CUnicode('#$$ -t 1-$n')
941 queue_regexp = CUnicode('#$$\W+-q\W+\$?\w+')
942 queue_template = CUnicode('#$$ -q $queue')
965 job_array_regexp = Unicode('#\$\W+\-t')
966 job_array_template = Unicode('#$ -t 1-{n}')
967 queue_regexp = Unicode('#\$\W+-q\W+\$?\w+')
968 queue_template = Unicode('#$ -q $queue')
943 969
944 970 class SGEControllerLauncher(SGELauncher):
945 971 """Launch a controller using SGE."""
946 972
947 batch_file_name = CUnicode(u'sge_controller', config=True)
948 default_template= CUnicode(u"""#$$ -V
949 #$$ -S /bin/sh
950 #$$ -N ipcontroller
951 %s --log-to-file --cluster-dir $cluster_dir
973 batch_file_name = Unicode(u'sge_controller', config=True,
974 help="batch file name for the ipontroller job.")
975 default_template= Unicode(u"""#$ -V
976 #$ -S /bin/sh
977 #$ -N ipcontroller
978 %s --log-to-file profile_dir={profile_dir}
952 979 """%(' '.join(ipcontroller_cmd_argv)))
953 980
954 def start(self, cluster_dir):
955 """Start the controller by profile or cluster_dir."""
981 def start(self, profile_dir):
982 """Start the controller by profile or profile_dir."""
956 983 self.log.info("Starting PBSControllerLauncher: %r" % self.args)
957 return super(PBSControllerLauncher, self).start(1, cluster_dir)
984 return super(SGEControllerLauncher, self).start(1, profile_dir)
958 985
959 986 class SGEEngineSetLauncher(SGELauncher):
960 987 """Launch Engines with SGE"""
961 batch_file_name = CUnicode(u'sge_engines', config=True)
962 default_template = CUnicode("""#$$ -V
963 #$$ -S /bin/sh
964 #$$ -N ipengine
965 %s --cluster-dir $cluster_dir
988 batch_file_name = Unicode(u'sge_engines', config=True,
989 help="batch file name for the engine(s) job.")
990 default_template = Unicode("""#$ -V
991 #$ -S /bin/sh
992 #$ -N ipengine
993 %s profile_dir={profile_dir}
966 994 """%(' '.join(ipengine_cmd_argv)))
967 995
968 def start(self, n, cluster_dir):
969 """Start n engines by profile or cluster_dir."""
996 def start(self, n, profile_dir):
997 """Start n engines by profile or profile_dir."""
970 998 self.log.info('Starting %i engines with SGEEngineSetLauncher: %r' % (n, self.args))
971 return super(SGEEngineSetLauncher, self).start(n, cluster_dir)
999 return super(SGEEngineSetLauncher, self).start(n, profile_dir)
972 1000
973 1001
974 1002 #-----------------------------------------------------------------------------
@@ -979,18 +1007,57 b' class SGEEngineSetLauncher(SGELauncher):'
979 1007 class IPClusterLauncher(LocalProcessLauncher):
980 1008 """Launch the ipcluster program in an external process."""
981 1009
982 ipcluster_cmd = List(ipcluster_cmd_argv, config=True)
983 # Command line arguments to pass to ipcluster.
1010 ipcluster_cmd = List(ipcluster_cmd_argv, config=True,
1011 help="Popen command for ipcluster")
984 1012 ipcluster_args = List(
985 ['--clean-logs', '--log-to-file', '--log-level', str(logging.INFO)], config=True)
986 ipcluster_subcommand = Str('start')
1013 ['--clean-logs', '--log-to-file', 'log_level=%i'%logging.INFO], config=True,
1014 help="Command line arguments to pass to ipcluster.")
1015 ipcluster_subcommand = Unicode('start')
987 1016 ipcluster_n = Int(2)
988 1017
989 1018 def find_args(self):
990 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
991 ['-n', repr(self.ipcluster_n)] + self.ipcluster_args
1019 return self.ipcluster_cmd + ['--'+self.ipcluster_subcommand] + \
1020 ['n=%i'%self.ipcluster_n] + self.ipcluster_args
992 1021
993 1022 def start(self):
994 1023 self.log.info("Starting ipcluster: %r" % self.args)
995 1024 return super(IPClusterLauncher, self).start()
996 1025
1026 #-----------------------------------------------------------------------------
1027 # Collections of launchers
1028 #-----------------------------------------------------------------------------
1029
1030 local_launchers = [
1031 LocalControllerLauncher,
1032 LocalEngineLauncher,
1033 LocalEngineSetLauncher,
1034 ]
1035 mpi_launchers = [
1036 MPIExecLauncher,
1037 MPIExecControllerLauncher,
1038 MPIExecEngineSetLauncher,
1039 ]
1040 ssh_launchers = [
1041 SSHLauncher,
1042 SSHControllerLauncher,
1043 SSHEngineLauncher,
1044 SSHEngineSetLauncher,
1045 ]
1046 winhpc_launchers = [
1047 WindowsHPCLauncher,
1048 WindowsHPCControllerLauncher,
1049 WindowsHPCEngineSetLauncher,
1050 ]
1051 pbs_launchers = [
1052 PBSLauncher,
1053 PBSControllerLauncher,
1054 PBSEngineSetLauncher,
1055 ]
1056 sge_launchers = [
1057 SGELauncher,
1058 SGEControllerLauncher,
1059 SGEEngineSetLauncher,
1060 ]
1061 all_launchers = local_launchers + mpi_launchers + ssh_launchers + winhpc_launchers\
1062 + pbs_launchers + sge_launchers
1063
@@ -1,5 +1,12 b''
1 1 #!/usr/bin/env python
2 """A simple logger object that consolidates messages incoming from ipcluster processes."""
2 """
3 A simple logger object that consolidates messages incoming from ipcluster processes.
4
5 Authors:
6
7 * MinRK
8
9 """
3 10
4 11 #-----------------------------------------------------------------------------
5 12 # Copyright (C) 2011 The IPython Development Team
@@ -19,29 +26,35 b' import sys'
19 26 import zmq
20 27 from zmq.eventloop import ioloop, zmqstream
21 28
22 from IPython.utils.traitlets import Int, Str, Instance, List
23
24 from IPython.parallel.factory import LoggingFactory
29 from IPython.config.configurable import LoggingConfigurable
30 from IPython.utils.traitlets import Int, Unicode, Instance, List
25 31
26 32 #-----------------------------------------------------------------------------
27 33 # Classes
28 34 #-----------------------------------------------------------------------------
29 35
30 36
31 class LogWatcher(LoggingFactory):
37 class LogWatcher(LoggingConfigurable):
32 38 """A simple class that receives messages on a SUB socket, as published
33 39 by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself.
34 40
35 41 This can subscribe to multiple topics, but defaults to all topics.
36 42 """
43
37 44 # configurables
38 topics = List([''], config=True)
39 url = Str('tcp://127.0.0.1:20202', config=True)
45 topics = List([''], config=True,
46 help="The ZMQ topics to subscribe to. Default is to subscribe to all messages")
47 url = Unicode('tcp://127.0.0.1:20202', config=True,
48 help="ZMQ url on which to listen for log messages")
40 49
41 50 # internals
42 context = Instance(zmq.Context, (), {})
43 51 stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
44 loop = Instance('zmq.eventloop.ioloop.IOLoop')
52
53 context = Instance(zmq.Context)
54 def _context_default(self):
55 return zmq.Context.instance()
56
57 loop = Instance(zmq.eventloop.ioloop.IOLoop)
45 58 def _loop_default(self):
46 59 return ioloop.IOLoop.instance()
47 60
@@ -62,9 +75,13 b' class LogWatcher(LoggingFactory):'
62 75 def subscribe(self):
63 76 """Update our SUB socket's subscriptions."""
64 77 self.stream.setsockopt(zmq.UNSUBSCRIBE, '')
65 for topic in self.topics:
66 self.log.debug("Subscribing to: %r"%topic)
67 self.stream.setsockopt(zmq.SUBSCRIBE, topic)
78 if '' in self.topics:
79 self.log.debug("Subscribing to: everything")
80 self.stream.setsockopt(zmq.SUBSCRIBE, '')
81 else:
82 for topic in self.topics:
83 self.log.debug("Subscribing to: %r"%(topic))
84 self.stream.setsockopt(zmq.SUBSCRIBE, topic)
68 85
69 86 def _extract_level(self, topic_str):
70 87 """Turn 'engine.0.INFO.extra' into (logging.INFO, 'engine.0.extra')"""
@@ -94,5 +111,5 b' class LogWatcher(LoggingFactory):'
94 111 level,topic = self._extract_level(topic)
95 112 if msg[-1] == '\n':
96 113 msg = msg[:-1]
97 logging.log(level, "[%s] %s" % (topic, msg))
114 self.log.log(level, "[%s] %s" % (topic, msg))
98 115
@@ -1,7 +1,13 b''
1 1 #!/usr/bin/env python
2 2 """Utility for forwarding file read events over a zmq socket.
3 3
4 This is necessary because select on Windows only supports"""
4 This is necessary because select on Windows only supports sockets, not FDs.
5
6 Authors:
7
8 * MinRK
9
10 """
5 11
6 12 #-----------------------------------------------------------------------------
7 13 # Copyright (C) 2011 The IPython Development Team
@@ -31,7 +37,7 b' class ForwarderThread(Thread):'
31 37 self.fd = fd
32 38
33 39 def run(self):
34 """loop through lines in self.fd, and send them over self.sock"""
40 """Loop through lines in self.fd, and send them over self.sock."""
35 41 line = self.fd.readline()
36 42 # allow for files opened in unicode mode
37 43 if isinstance(line, unicode):
@@ -46,7 +52,7 b' class ForwarderThread(Thread):'
46 52 self.sock.close()
47 53
48 54 def forward_read_events(fd, context=None):
49 """forward read events from an FD over a socket.
55 """Forward read events from an FD over a socket.
50 56
51 57 This method wraps a file in a socket pair, so it can
52 58 be polled for read events by select (specifically zmq.eventloop.ioloop)
@@ -64,4 +70,4 b' def forward_read_events(fd, context=None):'
64 70 return pull
65 71
66 72
67 __all__ = ['forward_read_events'] No newline at end of file
73 __all__ = ['forward_read_events']
@@ -3,10 +3,16 b''
3 3 """
4 4 Job and task components for writing .xml files that the Windows HPC Server
5 5 2008 can use to start jobs.
6
7 Authors:
8
9 * Brian Granger
10 * MinRK
11
6 12 """
7 13
8 14 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2009 The IPython Development Team
15 # Copyright (C) 2008-2011 The IPython Development Team
10 16 #
11 17 # Distributed under the terms of the BSD License. The full license is in
12 18 # the file COPYING, distributed as part of this software.
@@ -24,8 +30,8 b' from xml.etree import ElementTree as ET'
24 30
25 31 from IPython.config.configurable import Configurable
26 32 from IPython.utils.traitlets import (
27 Str, Int, List, Instance,
28 Enum, Bool, CStr
33 Unicode, Int, List, Instance,
34 Enum, Bool
29 35 )
30 36
31 37 #-----------------------------------------------------------------------------
@@ -74,27 +80,27 b' def find_username():'
74 80
75 81 class WinHPCJob(Configurable):
76 82
77 job_id = Str('')
78 job_name = Str('MyJob', config=True)
83 job_id = Unicode('')
84 job_name = Unicode('MyJob', config=True)
79 85 min_cores = Int(1, config=True)
80 86 max_cores = Int(1, config=True)
81 87 min_sockets = Int(1, config=True)
82 88 max_sockets = Int(1, config=True)
83 89 min_nodes = Int(1, config=True)
84 90 max_nodes = Int(1, config=True)
85 unit_type = Str("Core", config=True)
91 unit_type = Unicode("Core", config=True)
86 92 auto_calculate_min = Bool(True, config=True)
87 93 auto_calculate_max = Bool(True, config=True)
88 94 run_until_canceled = Bool(False, config=True)
89 95 is_exclusive = Bool(False, config=True)
90 username = Str(find_username(), config=True)
91 job_type = Str('Batch', config=True)
96 username = Unicode(find_username(), config=True)
97 job_type = Unicode('Batch', config=True)
92 98 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
93 99 default_value='Highest', config=True)
94 requested_nodes = Str('', config=True)
95 project = Str('IPython', config=True)
96 xmlns = Str('http://schemas.microsoft.com/HPCS2008/scheduler/')
97 version = Str("2.000")
100 requested_nodes = Unicode('', config=True)
101 project = Unicode('IPython', config=True)
102 xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/')
103 version = Unicode("2.000")
98 104 tasks = List([])
99 105
100 106 @property
@@ -165,21 +171,21 b' class WinHPCJob(Configurable):'
165 171
166 172 class WinHPCTask(Configurable):
167 173
168 task_id = Str('')
169 task_name = Str('')
170 version = Str("2.000")
174 task_id = Unicode('')
175 task_name = Unicode('')
176 version = Unicode("2.000")
171 177 min_cores = Int(1, config=True)
172 178 max_cores = Int(1, config=True)
173 179 min_sockets = Int(1, config=True)
174 180 max_sockets = Int(1, config=True)
175 181 min_nodes = Int(1, config=True)
176 182 max_nodes = Int(1, config=True)
177 unit_type = Str("Core", config=True)
178 command_line = CStr('', config=True)
179 work_directory = CStr('', config=True)
183 unit_type = Unicode("Core", config=True)
184 command_line = Unicode('', config=True)
185 work_directory = Unicode('', config=True)
180 186 is_rerunnaable = Bool(True, config=True)
181 std_out_file_path = CStr('', config=True)
182 std_err_file_path = CStr('', config=True)
187 std_out_file_path = Unicode('', config=True)
188 std_err_file_path = Unicode('', config=True)
183 189 is_parametric = Bool(False, config=True)
184 190 environment_variables = Instance(dict, args=(), config=True)
185 191
@@ -223,41 +229,41 b' class WinHPCTask(Configurable):'
223 229 # By declaring these, we can configure the controller and engine separately!
224 230
225 231 class IPControllerJob(WinHPCJob):
226 job_name = Str('IPController', config=False)
232 job_name = Unicode('IPController', config=False)
227 233 is_exclusive = Bool(False, config=True)
228 username = Str(find_username(), config=True)
234 username = Unicode(find_username(), config=True)
229 235 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
230 236 default_value='Highest', config=True)
231 requested_nodes = Str('', config=True)
232 project = Str('IPython', config=True)
237 requested_nodes = Unicode('', config=True)
238 project = Unicode('IPython', config=True)
233 239
234 240
235 241 class IPEngineSetJob(WinHPCJob):
236 job_name = Str('IPEngineSet', config=False)
242 job_name = Unicode('IPEngineSet', config=False)
237 243 is_exclusive = Bool(False, config=True)
238 username = Str(find_username(), config=True)
244 username = Unicode(find_username(), config=True)
239 245 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
240 246 default_value='Highest', config=True)
241 requested_nodes = Str('', config=True)
242 project = Str('IPython', config=True)
247 requested_nodes = Unicode('', config=True)
248 project = Unicode('IPython', config=True)
243 249
244 250
245 251 class IPControllerTask(WinHPCTask):
246 252
247 task_name = Str('IPController', config=True)
253 task_name = Unicode('IPController', config=True)
248 254 controller_cmd = List(['ipcontroller.exe'], config=True)
249 controller_args = List(['--log-to-file', '--log-level', '40'], config=True)
255 controller_args = List(['--log-to-file', 'log-level=40'], config=True)
250 256 # I don't want these to be configurable
251 std_out_file_path = CStr('', config=False)
252 std_err_file_path = CStr('', config=False)
257 std_out_file_path = Unicode('', config=False)
258 std_err_file_path = Unicode('', config=False)
253 259 min_cores = Int(1, config=False)
254 260 max_cores = Int(1, config=False)
255 261 min_sockets = Int(1, config=False)
256 262 max_sockets = Int(1, config=False)
257 263 min_nodes = Int(1, config=False)
258 264 max_nodes = Int(1, config=False)
259 unit_type = Str("Core", config=False)
260 work_directory = CStr('', config=False)
265 unit_type = Unicode("Core", config=False)
266 work_directory = Unicode('', config=False)
261 267
262 268 def __init__(self, config=None):
263 269 super(IPControllerTask, self).__init__(config=config)
@@ -272,20 +278,20 b' class IPControllerTask(WinHPCTask):'
272 278
273 279 class IPEngineTask(WinHPCTask):
274 280
275 task_name = Str('IPEngine', config=True)
281 task_name = Unicode('IPEngine', config=True)
276 282 engine_cmd = List(['ipengine.exe'], config=True)
277 engine_args = List(['--log-to-file', '--log-level', '40'], config=True)
283 engine_args = List(['--log-to-file', 'log_level=40'], config=True)
278 284 # I don't want these to be configurable
279 std_out_file_path = CStr('', config=False)
280 std_err_file_path = CStr('', config=False)
285 std_out_file_path = Unicode('', config=False)
286 std_err_file_path = Unicode('', config=False)
281 287 min_cores = Int(1, config=False)
282 288 max_cores = Int(1, config=False)
283 289 min_sockets = Int(1, config=False)
284 290 max_sockets = Int(1, config=False)
285 291 min_nodes = Int(1, config=False)
286 292 max_nodes = Int(1, config=False)
287 unit_type = Str("Core", config=False)
288 work_directory = CStr('', config=False)
293 unit_type = Unicode("Core", config=False)
294 work_directory = Unicode('', config=False)
289 295
290 296 def __init__(self, config=None):
291 297 super(IPEngineTask,self).__init__(config=config)
@@ -1,4 +1,9 b''
1 """AsyncResult objects for the client"""
1 """AsyncResult objects for the client
2
3 Authors:
4
5 * MinRK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
@@ -1,6 +1,11 b''
1 """A semi-synchronous Client for the ZMQ cluster"""
1 """A semi-synchronous Client for the ZMQ cluster
2
3 Authors:
4
5 * MinRK
6 """
2 7 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
5 10 # Distributed under the terms of the BSD License. The full license is in
6 11 # the file COPYING, distributed as part of this software.
@@ -23,18 +28,20 b' pjoin = os.path.join'
23 28 import zmq
24 29 # from zmq.eventloop import ioloop, zmqstream
25 30
31 from IPython.utils.jsonutil import rekey
26 32 from IPython.utils.path import get_ipython_dir
27 from IPython.utils.traitlets import (HasTraits, Int, Instance, CUnicode,
28 Dict, List, Bool, Str, Set)
33 from IPython.utils.traitlets import (HasTraits, Int, Instance, Unicode,
34 Dict, List, Bool, Set)
29 35 from IPython.external.decorator import decorator
30 36 from IPython.external.ssh import tunnel
31 37
32 38 from IPython.parallel import error
33 from IPython.parallel import streamsession as ss
34 39 from IPython.parallel import util
35 40
41 from IPython.zmq.session import Session, Message
42
36 43 from .asyncresult import AsyncResult, AsyncHubResult
37 from IPython.parallel.apps.clusterdir import ClusterDir, ClusterDirError
44 from IPython.core.profiledir import ProfileDir, ProfileDirError
38 45 from .view import DirectView, LoadBalancedView
39 46
40 47 #--------------------------------------------------------------------------
@@ -119,11 +126,27 b' class Client(HasTraits):'
119 126 [Default: 'default']
120 127 context : zmq.Context
121 128 Pass an existing zmq.Context instance, otherwise the client will create its own.
122 username : bytes
123 set username to be passed to the Session object
124 129 debug : bool
125 130 flag for lots of message printing for debug purposes
131 timeout : int/float
132 time (in seconds) to wait for connection replies from the Hub
133 [Default: 10]
126 134
135 #-------------- session related args ----------------
136
137 config : Config object
138 If specified, this will be relayed to the Session for configuration
139 username : str
140 set username for the session object
141 packer : str (import_string) or callable
142 Can be either the simple keyword 'json' or 'pickle', or an import_string to a
143 function to serialize messages. Must support same input as
144 JSON, and output must be bytes.
145 You can pass a callable directly as `pack`
146 unpacker : str (import_string) or callable
147 The inverse of packer. Only necessary if packer is specified as *not* one
148 of 'json' or 'pickle'.
149
127 150 #-------------- ssh related args ----------------
128 151 # These are args for configuring the ssh tunnel to be used
129 152 # credentials are used to forward connections over ssh to the Controller
@@ -149,9 +172,10 b' class Client(HasTraits):'
149 172
150 173 ------- exec authentication args -------
151 174 If even localhost is untrusted, you can have some protection against
152 unauthorized execution by using a key. Messages are still sent
153 as cleartext, so if someone can snoop your loopback traffic this will
154 not help against malicious attacks.
175 unauthorized execution by signing messages with HMAC digests.
176 Messages are still sent as cleartext, so if someone can snoop your
177 loopback traffic this will not protect your privacy, but will prevent
178 unauthorized execution.
155 179
156 180 exec_key : str
157 181 an authentication key or file containing a key
@@ -213,7 +237,7 b' class Client(HasTraits):'
213 237 metadata = Instance('collections.defaultdict', (Metadata,))
214 238 history = List()
215 239 debug = Bool(False)
216 profile=CUnicode('default')
240 profile=Unicode('default')
217 241
218 242 _outstanding_dict = Instance('collections.defaultdict', (set,))
219 243 _ids = List()
@@ -229,15 +253,15 b' class Client(HasTraits):'
229 253 _notification_socket=Instance('zmq.Socket')
230 254 _mux_socket=Instance('zmq.Socket')
231 255 _task_socket=Instance('zmq.Socket')
232 _task_scheme=Str()
256 _task_scheme=Unicode()
233 257 _closed = False
234 258 _ignored_control_replies=Int(0)
235 259 _ignored_hub_replies=Int(0)
236 260
237 def __init__(self, url_or_file=None, profile='default', cluster_dir=None, ipython_dir=None,
238 context=None, username=None, debug=False, exec_key=None,
261 def __init__(self, url_or_file=None, profile='default', profile_dir=None, ipython_dir=None,
262 context=None, debug=False, exec_key=None,
239 263 sshserver=None, sshkey=None, password=None, paramiko=None,
240 timeout=10
264 timeout=10, **extra_args
241 265 ):
242 266 super(Client, self).__init__(debug=debug, profile=profile)
243 267 if context is None:
@@ -245,7 +269,7 b' class Client(HasTraits):'
245 269 self._context = context
246 270
247 271
248 self._setup_cluster_dir(profile, cluster_dir, ipython_dir)
272 self._setup_profile_dir(profile, profile_dir, ipython_dir)
249 273 if self._cd is not None:
250 274 if url_or_file is None:
251 275 url_or_file = pjoin(self._cd.security_dir, 'ipcontroller-client.json')
@@ -288,15 +312,15 b' class Client(HasTraits):'
288 312 else:
289 313 password = getpass("SSH Password for %s: "%sshserver)
290 314 ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko)
291 if exec_key is not None and os.path.isfile(exec_key):
292 arg = 'keyfile'
293 else:
294 arg = 'key'
295 key_arg = {arg:exec_key}
296 if username is None:
297 self.session = ss.StreamSession(**key_arg)
298 else:
299 self.session = ss.StreamSession(username, **key_arg)
315
316 # configure and construct the session
317 if exec_key is not None:
318 if os.path.isfile(exec_key):
319 extra_args['keyfile'] = exec_key
320 else:
321 extra_args['key'] = exec_key
322 self.session = Session(**extra_args)
323
300 324 self._query_socket = self._context.socket(zmq.XREQ)
301 325 self._query_socket.setsockopt(zmq.IDENTITY, self.session.session)
302 326 if self._ssh:
@@ -318,21 +342,21 b' class Client(HasTraits):'
318 342 """cleanup sockets, but _not_ context."""
319 343 self.close()
320 344
321 def _setup_cluster_dir(self, profile, cluster_dir, ipython_dir):
345 def _setup_profile_dir(self, profile, profile_dir, ipython_dir):
322 346 if ipython_dir is None:
323 347 ipython_dir = get_ipython_dir()
324 if cluster_dir is not None:
348 if profile_dir is not None:
325 349 try:
326 self._cd = ClusterDir.find_cluster_dir(cluster_dir)
350 self._cd = ProfileDir.find_profile_dir(profile_dir)
327 351 return
328 except ClusterDirError:
352 except ProfileDirError:
329 353 pass
330 354 elif profile is not None:
331 355 try:
332 self._cd = ClusterDir.find_cluster_dir_by_profile(
356 self._cd = ProfileDir.find_profile_dir_by_name(
333 357 ipython_dir, profile)
334 358 return
335 except ClusterDirError:
359 except ProfileDirError:
336 360 pass
337 361 self._cd = None
338 362
@@ -416,7 +440,7 b' class Client(HasTraits):'
416 440 idents,msg = self.session.recv(self._query_socket,mode=0)
417 441 if self.debug:
418 442 pprint(msg)
419 msg = ss.Message(msg)
443 msg = Message(msg)
420 444 content = msg.content
421 445 self._config['registration'] = dict(content)
422 446 if content.status == 'ok':
@@ -478,11 +502,11 b' class Client(HasTraits):'
478 502 md['engine_id'] = self._engines.get(md['engine_uuid'], None)
479 503
480 504 if 'date' in parent:
481 md['submitted'] = datetime.strptime(parent['date'], util.ISO8601)
505 md['submitted'] = parent['date']
482 506 if 'started' in header:
483 md['started'] = datetime.strptime(header['started'], util.ISO8601)
507 md['started'] = header['started']
484 508 if 'date' in header:
485 md['completed'] = datetime.strptime(header['date'], util.ISO8601)
509 md['completed'] = header['date']
486 510 return md
487 511
488 512 def _register_engine(self, msg):
@@ -528,7 +552,7 b' class Client(HasTraits):'
528 552 header = {}
529 553 parent['msg_id'] = msg_id
530 554 header['engine'] = uuid
531 header['date'] = datetime.now().strftime(util.ISO8601)
555 header['date'] = datetime.now()
532 556 msg = dict(parent_header=parent, header=header, content=content)
533 557 self._handle_apply_reply(msg)
534 558
@@ -589,33 +613,31 b' class Client(HasTraits):'
589 613 def _flush_notifications(self):
590 614 """Flush notifications of engine registrations waiting
591 615 in ZMQ queue."""
592 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
616 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
593 617 while msg is not None:
594 618 if self.debug:
595 619 pprint(msg)
596 msg = msg[-1]
597 620 msg_type = msg['msg_type']
598 621 handler = self._notification_handlers.get(msg_type, None)
599 622 if handler is None:
600 623 raise Exception("Unhandled message type: %s"%msg.msg_type)
601 624 else:
602 625 handler(msg)
603 msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
626 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
604 627
605 628 def _flush_results(self, sock):
606 629 """Flush task or queue results waiting in ZMQ queue."""
607 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
630 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
608 631 while msg is not None:
609 632 if self.debug:
610 633 pprint(msg)
611 msg = msg[-1]
612 634 msg_type = msg['msg_type']
613 635 handler = self._queue_handlers.get(msg_type, None)
614 636 if handler is None:
615 637 raise Exception("Unhandled message type: %s"%msg.msg_type)
616 638 else:
617 639 handler(msg)
618 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
640 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
619 641
620 642 def _flush_control(self, sock):
621 643 """Flush replies from the control channel waiting
@@ -624,12 +646,12 b' class Client(HasTraits):'
624 646 Currently: ignore them."""
625 647 if self._ignored_control_replies <= 0:
626 648 return
627 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
649 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
628 650 while msg is not None:
629 651 self._ignored_control_replies -= 1
630 652 if self.debug:
631 653 pprint(msg)
632 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
654 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
633 655
634 656 def _flush_ignored_control(self):
635 657 """flush ignored control replies"""
@@ -638,19 +660,18 b' class Client(HasTraits):'
638 660 self._ignored_control_replies -= 1
639 661
640 662 def _flush_ignored_hub_replies(self):
641 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
663 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
642 664 while msg is not None:
643 msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
665 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
644 666
645 667 def _flush_iopub(self, sock):
646 668 """Flush replies from the iopub channel waiting
647 669 in the ZMQ queue.
648 670 """
649 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
671 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
650 672 while msg is not None:
651 673 if self.debug:
652 674 pprint(msg)
653 msg = msg[-1]
654 675 parent = msg['parent_header']
655 676 msg_id = parent['msg_id']
656 677 content = msg['content']
@@ -674,7 +695,7 b' class Client(HasTraits):'
674 695 # reduntant?
675 696 self.metadata[msg_id] = md
676 697
677 msg = self.session.recv(sock, mode=zmq.NOBLOCK)
698 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
678 699
679 700 #--------------------------------------------------------------------------
680 701 # len, getitem
@@ -1172,6 +1193,7 b' class Client(HasTraits):'
1172 1193 failures = []
1173 1194 # load cached results into result:
1174 1195 content.update(local_results)
1196
1175 1197 # update cache with results:
1176 1198 for msg_id in sorted(theids):
1177 1199 if msg_id in content['completed']:
@@ -1226,7 +1248,7 b' class Client(HasTraits):'
1226 1248 status = content.pop('status')
1227 1249 if status != 'ok':
1228 1250 raise self._unwrap_exception(content)
1229 content = util.rekey(content)
1251 content = rekey(content)
1230 1252 if isinstance(targets, int):
1231 1253 return content[targets]
1232 1254 else:
@@ -1332,6 +1354,7 b' class Client(HasTraits):'
1332 1354 raise self._unwrap_exception(content)
1333 1355
1334 1356 records = content['records']
1357
1335 1358 buffer_lens = content['buffer_lens']
1336 1359 result_buffer_lens = content['result_buffer_lens']
1337 1360 buffers = msg['buffers']
@@ -1345,11 +1368,6 b' class Client(HasTraits):'
1345 1368 if has_rbufs:
1346 1369 blen = result_buffer_lens[i]
1347 1370 rec['result_buffers'], buffers = buffers[:blen],buffers[blen:]
1348 # turn timestamps back into times
1349 for key in 'submitted started completed resubmitted'.split():
1350 maybedate = rec.get(key, None)
1351 if maybedate and util.ISO8601_RE.match(maybedate):
1352 rec[key] = datetime.strptime(maybedate, util.ISO8601)
1353 1371
1354 1372 return records
1355 1373
@@ -4,12 +4,19 b''
4 4
5 5 Scattering consists of partitioning a sequence and sending the various
6 6 pieces to individual nodes in a cluster.
7
8
9 Authors:
10
11 * Brian Granger
12 * MinRK
13
7 14 """
8 15
9 16 __docformat__ = "restructuredtext en"
10 17
11 18 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
19 # Copyright (C) 2008-2011 The IPython Development Team
13 20 #
14 21 # Distributed under the terms of the BSD License. The full license is in
15 22 # the file COPYING, distributed as part of this software.
@@ -1,6 +1,12 b''
1 """Remote Functions and decorators for Views."""
1 """Remote Functions and decorators for Views.
2
3 Authors:
4
5 * Brian Granger
6 * Min RK
7 """
2 8 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
9 # Copyright (C) 2010-2011 The IPython Development Team
4 10 #
5 11 # Distributed under the terms of the BSD License. The full license is in
6 12 # the file COPYING, distributed as part of this software.
@@ -1,6 +1,11 b''
1 """Views of remote engines."""
1 """Views of remote engines.
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
5 10 # Distributed under the terms of the BSD License. The full license is in
6 11 # the file COPYING, distributed as part of this software.
@@ -1,4 +1,9 b''
1 """Dependency utilities"""
1 """Dependency utilities
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
@@ -1,6 +1,11 b''
1 1 """A Task logger that presents our DB interface,
2 2 but exists entirely in memory and implemented with dicts.
3 3
4 Authors:
5
6 * Min RK
7
8
4 9 TaskRecords are dicts of the form:
5 10 {
6 11 'msg_id' : str(uuid),
@@ -35,7 +40,7 b' We support a subset of mongodb operators:'
35 40 $lt,$gt,$lte,$gte,$ne,$in,$nin,$all,$mod,$exists
36 41 """
37 42 #-----------------------------------------------------------------------------
38 # Copyright (C) 2010 The IPython Development Team
43 # Copyright (C) 2010-2011 The IPython Development Team
39 44 #
40 45 # Distributed under the terms of the BSD License. The full license is in
41 46 # the file COPYING, distributed as part of this software.
@@ -44,9 +49,9 b' We support a subset of mongodb operators:'
44 49
45 50 from datetime import datetime
46 51
47 from IPython.config.configurable import Configurable
52 from IPython.config.configurable import LoggingConfigurable
48 53
49 from IPython.utils.traitlets import Dict, CUnicode
54 from IPython.utils.traitlets import Dict, Unicode, Instance
50 55
51 56 filters = {
52 57 '$lt' : lambda a,b: a < b,
@@ -79,10 +84,10 b' class CompositeFilter(object):'
79 84 return False
80 85 return True
81 86
82 class BaseDB(Configurable):
87 class BaseDB(LoggingConfigurable):
83 88 """Empty Parent class so traitlets work on DB."""
84 89 # base configurable traits:
85 session = CUnicode("")
90 session = Unicode("")
86 91
87 92 class DictDB(BaseDB):
88 93 """Basic in-memory dict-based object for saving Task Records.
@@ -2,6 +2,10 b''
2 2 """
3 3 A multi-heart Heartbeat system using PUB and XREP sockets. pings are sent out on the PUB,
4 4 and hearts are tracked based on their XREQ identities.
5
6 Authors:
7
8 * Min RK
5 9 """
6 10 #-----------------------------------------------------------------------------
7 11 # Copyright (C) 2010-2011 The IPython Development Team
@@ -15,11 +19,11 b' import time'
15 19 import uuid
16 20
17 21 import zmq
18 from zmq.devices import ProcessDevice, ThreadDevice
22 from zmq.devices import ThreadDevice
19 23 from zmq.eventloop import ioloop, zmqstream
20 24
21 from IPython.utils.traitlets import Set, Instance, CFloat, Bool
22 from IPython.parallel.factory import LoggingFactory
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.traitlets import Set, Instance, CFloat
23 27
24 28 class Heart(object):
25 29 """A basic heart object for responding to a HeartMonitor.
@@ -47,20 +51,22 b' class Heart(object):'
47 51 def start(self):
48 52 return self.device.start()
49 53
50 class HeartMonitor(LoggingFactory):
54 class HeartMonitor(LoggingConfigurable):
51 55 """A basic HeartMonitor class
52 56 pingstream: a PUB stream
53 57 pongstream: an XREP stream
54 58 period: the period of the heartbeat in milliseconds"""
55 59
56 period=CFloat(1000, config=True) # in milliseconds
60 period=CFloat(1000, config=True,
61 help='The frequency at which the Hub pings the engines for heartbeats '
62 ' (in ms) [default: 100]',
63 )
57 64
58 65 pingstream=Instance('zmq.eventloop.zmqstream.ZMQStream')
59 66 pongstream=Instance('zmq.eventloop.zmqstream.ZMQStream')
60 67 loop = Instance('zmq.eventloop.ioloop.IOLoop')
61 68 def _loop_default(self):
62 69 return ioloop.IOLoop.instance()
63 debug=Bool(False)
64 70
65 71 # not settable:
66 72 hearts=Set()
@@ -2,6 +2,10 b''
2 2 """The IPython Controller Hub with 0MQ
3 3 This is the master object that handles connections from engines and clients,
4 4 and monitors traffic through the various queues.
5
6 Authors:
7
8 * Min RK
5 9 """
6 10 #-----------------------------------------------------------------------------
7 11 # Copyright (C) 2010 The IPython Development Team
@@ -25,10 +29,14 b' from zmq.eventloop.zmqstream import ZMQStream'
25 29
26 30 # internal:
27 31 from IPython.utils.importstring import import_item
28 from IPython.utils.traitlets import HasTraits, Instance, Int, CStr, Str, Dict, Set, List, Bool
32 from IPython.utils.traitlets import (
33 HasTraits, Instance, Int, Unicode, Dict, Set, Tuple, CStr
34 )
29 35
30 36 from IPython.parallel import error, util
31 from IPython.parallel.factory import RegistrationFactory, LoggingFactory
37 from IPython.parallel.factory import RegistrationFactory
38
39 from IPython.zmq.session import SessionFactory
32 40
33 41 from .heartmonitor import HeartMonitor
34 42
@@ -75,7 +83,7 b' def init_record(msg):'
75 83 'header' : header,
76 84 'content': msg['content'],
77 85 'buffers': msg['buffers'],
78 'submitted': datetime.strptime(header['date'], util.ISO8601),
86 'submitted': header['date'],
79 87 'client_uuid' : None,
80 88 'engine_uuid' : None,
81 89 'started': None,
@@ -103,68 +111,80 b' class EngineConnector(HasTraits):'
103 111 heartbeat (str): identity of heartbeat XREQ socket
104 112 """
105 113 id=Int(0)
106 queue=Str()
107 control=Str()
108 registration=Str()
109 heartbeat=Str()
114 queue=CStr()
115 control=CStr()
116 registration=CStr()
117 heartbeat=CStr()
110 118 pending=Set()
111 119
112 120 class HubFactory(RegistrationFactory):
113 121 """The Configurable for setting up a Hub."""
114 122
115 # name of a scheduler scheme
116 scheme = Str('leastload', config=True)
117
118 123 # port-pairs for monitoredqueues:
119 hb = Instance(list, config=True)
124 hb = Tuple(Int,Int,config=True,
125 help="""XREQ/SUB Port pair for Engine heartbeats""")
120 126 def _hb_default(self):
121 return util.select_random_ports(2)
127 return tuple(util.select_random_ports(2))
128
129 mux = Tuple(Int,Int,config=True,
130 help="""Engine/Client Port pair for MUX queue""")
122 131
123 mux = Instance(list, config=True)
124 132 def _mux_default(self):
125 return util.select_random_ports(2)
133 return tuple(util.select_random_ports(2))
126 134
127 task = Instance(list, config=True)
135 task = Tuple(Int,Int,config=True,
136 help="""Engine/Client Port pair for Task queue""")
128 137 def _task_default(self):
129 return util.select_random_ports(2)
138 return tuple(util.select_random_ports(2))
139
140 control = Tuple(Int,Int,config=True,
141 help="""Engine/Client Port pair for Control queue""")
130 142
131 control = Instance(list, config=True)
132 143 def _control_default(self):
133 return util.select_random_ports(2)
144 return tuple(util.select_random_ports(2))
145
146 iopub = Tuple(Int,Int,config=True,
147 help="""Engine/Client Port pair for IOPub relay""")
134 148
135 iopub = Instance(list, config=True)
136 149 def _iopub_default(self):
137 return util.select_random_ports(2)
150 return tuple(util.select_random_ports(2))
138 151
139 152 # single ports:
140 mon_port = Instance(int, config=True)
153 mon_port = Int(config=True,
154 help="""Monitor (SUB) port for queue traffic""")
155
141 156 def _mon_port_default(self):
142 157 return util.select_random_ports(1)[0]
143 158
144 notifier_port = Instance(int, config=True)
159 notifier_port = Int(config=True,
160 help="""PUB port for sending engine status notifications""")
161
145 162 def _notifier_port_default(self):
146 163 return util.select_random_ports(1)[0]
147 164
148 ping = Int(1000, config=True) # ping frequency
165 engine_ip = Unicode('127.0.0.1', config=True,
166 help="IP on which to listen for engine connections. [default: loopback]")
167 engine_transport = Unicode('tcp', config=True,
168 help="0MQ transport for engine connections. [default: tcp]")
149 169
150 engine_ip = CStr('127.0.0.1', config=True)
151 engine_transport = CStr('tcp', config=True)
170 client_ip = Unicode('127.0.0.1', config=True,
171 help="IP on which to listen for client connections. [default: loopback]")
172 client_transport = Unicode('tcp', config=True,
173 help="0MQ transport for client connections. [default : tcp]")
152 174
153 client_ip = CStr('127.0.0.1', config=True)
154 client_transport = CStr('tcp', config=True)
175 monitor_ip = Unicode('127.0.0.1', config=True,
176 help="IP on which to listen for monitor messages. [default: loopback]")
177 monitor_transport = Unicode('tcp', config=True,
178 help="0MQ transport for monitor messages. [default : tcp]")
155 179
156 monitor_ip = CStr('127.0.0.1', config=True)
157 monitor_transport = CStr('tcp', config=True)
180 monitor_url = Unicode('')
158 181
159 monitor_url = CStr('')
160
161 db_class = CStr('IPython.parallel.controller.dictdb.DictDB', config=True)
182 db_class = Unicode('IPython.parallel.controller.dictdb.DictDB', config=True,
183 help="""The class to use for the DB backend""")
162 184
163 185 # not configurable
164 186 db = Instance('IPython.parallel.controller.dictdb.BaseDB')
165 187 heartmonitor = Instance('IPython.parallel.controller.heartmonitor.HeartMonitor')
166 subconstructors = List()
167 _constructed = Bool(False)
168 188
169 189 def _ip_changed(self, name, old, new):
170 190 self.engine_ip = new
@@ -184,26 +204,16 b' class HubFactory(RegistrationFactory):'
184 204 def __init__(self, **kwargs):
185 205 super(HubFactory, self).__init__(**kwargs)
186 206 self._update_monitor_url()
187 # self.on_trait_change(self._sync_ips, 'ip')
188 # self.on_trait_change(self._sync_transports, 'transport')
189 self.subconstructors.append(self.construct_hub)
190 207
191 208
192 209 def construct(self):
193 assert not self._constructed, "already constructed!"
194
195 for subc in self.subconstructors:
196 subc()
197
198 self._constructed = True
199
210 self.init_hub()
200 211
201 212 def start(self):
202 assert self._constructed, "must be constructed by self.construct() first!"
203 213 self.heartmonitor.start()
204 214 self.log.info("Heartmonitor started")
205 215
206 def construct_hub(self):
216 def init_hub(self):
207 217 """construct"""
208 218 client_iface = "%s://%s:"%(self.client_transport, self.client_ip) + "%i"
209 219 engine_iface = "%s://%s:"%(self.engine_transport, self.engine_ip) + "%i"
@@ -226,8 +236,10 b' class HubFactory(RegistrationFactory):'
226 236 hpub.bind(engine_iface % self.hb[0])
227 237 hrep = ctx.socket(zmq.XREP)
228 238 hrep.bind(engine_iface % self.hb[1])
229 self.heartmonitor = HeartMonitor(loop=loop, pingstream=ZMQStream(hpub,loop), pongstream=ZMQStream(hrep,loop),
230 period=self.ping, logname=self.log.name)
239 self.heartmonitor = HeartMonitor(loop=loop, config=self.config, log=self.log,
240 pingstream=ZMQStream(hpub,loop),
241 pongstream=ZMQStream(hrep,loop)
242 )
231 243
232 244 ### Client connections ###
233 245 # Notifier socket
@@ -246,9 +258,14 b' class HubFactory(RegistrationFactory):'
246 258 # connect the db
247 259 self.log.info('Hub using DB backend: %r'%(self.db_class.split()[-1]))
248 260 # cdir = self.config.Global.cluster_dir
249 self.db = import_item(self.db_class)(session=self.session.session, config=self.config)
261 self.db = import_item(str(self.db_class))(session=self.session.session,
262 config=self.config, log=self.log)
250 263 time.sleep(.25)
251
264 try:
265 scheme = self.config.TaskScheduler.scheme_name
266 except AttributeError:
267 from .scheduler import TaskScheduler
268 scheme = TaskScheduler.scheme_name.get_default_value()
252 269 # build connection dicts
253 270 self.engine_info = {
254 271 'control' : engine_iface%self.control[1],
@@ -262,7 +279,7 b' class HubFactory(RegistrationFactory):'
262 279 self.client_info = {
263 280 'control' : client_iface%self.control[0],
264 281 'mux': client_iface%self.mux[0],
265 'task' : (self.scheme, client_iface%self.task[0]),
282 'task' : (scheme, client_iface%self.task[0]),
266 283 'iopub' : client_iface%self.iopub[0],
267 284 'notification': client_iface%self.notifier_port
268 285 }
@@ -278,16 +295,16 b' class HubFactory(RegistrationFactory):'
278 295 self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor,
279 296 query=q, notifier=n, resubmit=r, db=self.db,
280 297 engine_info=self.engine_info, client_info=self.client_info,
281 logname=self.log.name)
298 log=self.log)
282 299
283 300
284 class Hub(LoggingFactory):
301 class Hub(SessionFactory):
285 302 """The IPython Controller Hub with 0MQ connections
286 303
287 304 Parameters
288 305 ==========
289 306 loop: zmq IOLoop instance
290 session: StreamSession object
307 session: Session object
291 308 <removed> context: zmq context for creating new connections (?)
292 309 queue: ZMQStream for monitoring the command queue (SUB)
293 310 query: ZMQStream for engine registration and client queries requests (XREP)
@@ -319,7 +336,6 b' class Hub(LoggingFactory):'
319 336 _idcounter=Int(0)
320 337
321 338 # objects from constructor:
322 loop=Instance(ioloop.IOLoop)
323 339 query=Instance(ZMQStream)
324 340 monitor=Instance(ZMQStream)
325 341 notifier=Instance(ZMQStream)
@@ -438,34 +454,16 b' class Hub(LoggingFactory):'
438 454 # dispatch methods (1 per stream)
439 455 #-----------------------------------------------------------------------------
440 456
441 # def dispatch_registration_request(self, msg):
442 # """"""
443 # self.log.debug("registration::dispatch_register_request(%s)"%msg)
444 # idents,msg = self.session.feed_identities(msg)
445 # if not idents:
446 # self.log.error("Bad Query Message: %s"%msg, exc_info=True)
447 # return
448 # try:
449 # msg = self.session.unpack_message(msg,content=True)
450 # except:
451 # self.log.error("registration::got bad registration message: %s"%msg, exc_info=True)
452 # return
453 #
454 # msg_type = msg['msg_type']
455 # content = msg['content']
456 #
457 # handler = self.query_handlers.get(msg_type, None)
458 # if handler is None:
459 # self.log.error("registration::got bad registration message: %s"%msg)
460 # else:
461 # handler(idents, msg)
462 457
463 458 def dispatch_monitor_traffic(self, msg):
464 459 """all ME and Task queue messages come through here, as well as
465 460 IOPub traffic."""
466 461 self.log.debug("monitor traffic: %r"%msg[:2])
467 462 switch = msg[0]
468 idents, msg = self.session.feed_identities(msg[1:])
463 try:
464 idents, msg = self.session.feed_identities(msg[1:])
465 except ValueError:
466 idents=[]
469 467 if not idents:
470 468 self.log.error("Bad Monitor Message: %r"%msg)
471 469 return
@@ -478,20 +476,22 b' class Hub(LoggingFactory):'
478 476
479 477 def dispatch_query(self, msg):
480 478 """Route registration requests and queries from clients."""
481 idents, msg = self.session.feed_identities(msg)
479 try:
480 idents, msg = self.session.feed_identities(msg)
481 except ValueError:
482 idents = []
482 483 if not idents:
483 484 self.log.error("Bad Query Message: %r"%msg)
484 485 return
485 486 client_id = idents[0]
486 487 try:
487 488 msg = self.session.unpack_message(msg, content=True)
488 except:
489 except Exception:
489 490 content = error.wrap_exception()
490 491 self.log.error("Bad Query Message: %r"%msg, exc_info=True)
491 492 self.session.send(self.query, "hub_error", ident=client_id,
492 493 content=content)
493 494 return
494
495 495 # print client_id, header, parent, content
496 496 #switch on message type:
497 497 msg_type = msg['msg_type']
@@ -546,24 +546,22 b' class Hub(LoggingFactory):'
546 546
547 547 def save_queue_request(self, idents, msg):
548 548 if len(idents) < 2:
549 self.log.error("invalid identity prefix: %s"%idents)
549 self.log.error("invalid identity prefix: %r"%idents)
550 550 return
551 551 queue_id, client_id = idents[:2]
552 552 try:
553 msg = self.session.unpack_message(msg, content=False)
554 except:
555 self.log.error("queue::client %r sent invalid message to %r: %s"%(client_id, queue_id, msg), exc_info=True)
553 msg = self.session.unpack_message(msg)
554 except Exception:
555 self.log.error("queue::client %r sent invalid message to %r: %r"%(client_id, queue_id, msg), exc_info=True)
556 556 return
557 557
558 558 eid = self.by_ident.get(queue_id, None)
559 559 if eid is None:
560 560 self.log.error("queue::target %r not registered"%queue_id)
561 self.log.debug("queue:: valid are: %s"%(self.by_ident.keys()))
561 self.log.debug("queue:: valid are: %r"%(self.by_ident.keys()))
562 562 return
563
564 header = msg['header']
565 msg_id = header['msg_id']
566 563 record = init_record(msg)
564 msg_id = record['msg_id']
567 565 record['engine_uuid'] = queue_id
568 566 record['client_uuid'] = client_id
569 567 record['queue'] = 'mux'
@@ -577,30 +575,36 b' class Hub(LoggingFactory):'
577 575 self.log.warn("conflicting initial state for record: %r:%r <%r> %r"%(msg_id, rvalue, key, evalue))
578 576 elif evalue and not rvalue:
579 577 record[key] = evalue
580 self.db.update_record(msg_id, record)
578 try:
579 self.db.update_record(msg_id, record)
580 except Exception:
581 self.log.error("DB Error updating record %r"%msg_id, exc_info=True)
581 582 except KeyError:
582 self.db.add_record(msg_id, record)
583 try:
584 self.db.add_record(msg_id, record)
585 except Exception:
586 self.log.error("DB Error adding record %r"%msg_id, exc_info=True)
587
583 588
584 589 self.pending.add(msg_id)
585 590 self.queues[eid].append(msg_id)
586 591
587 592 def save_queue_result(self, idents, msg):
588 593 if len(idents) < 2:
589 self.log.error("invalid identity prefix: %s"%idents)
594 self.log.error("invalid identity prefix: %r"%idents)
590 595 return
591 596
592 597 client_id, queue_id = idents[:2]
593 598 try:
594 msg = self.session.unpack_message(msg, content=False)
595 except:
596 self.log.error("queue::engine %r sent invalid message to %r: %s"%(
599 msg = self.session.unpack_message(msg)
600 except Exception:
601 self.log.error("queue::engine %r sent invalid message to %r: %r"%(
597 602 queue_id,client_id, msg), exc_info=True)
598 603 return
599 604
600 605 eid = self.by_ident.get(queue_id, None)
601 606 if eid is None:
602 607 self.log.error("queue::unknown engine %r is sending a reply: "%queue_id)
603 # self.log.debug("queue:: %s"%msg[2:])
604 608 return
605 609
606 610 parent = msg['parent_header']
@@ -615,14 +619,12 b' class Hub(LoggingFactory):'
615 619 elif msg_id not in self.all_completed:
616 620 # it could be a result from a dead engine that died before delivering the
617 621 # result
618 self.log.warn("queue:: unknown msg finished %s"%msg_id)
622 self.log.warn("queue:: unknown msg finished %r"%msg_id)
619 623 return
620 624 # update record anyway, because the unregistration could have been premature
621 625 rheader = msg['header']
622 completed = datetime.strptime(rheader['date'], util.ISO8601)
626 completed = rheader['date']
623 627 started = rheader.get('started', None)
624 if started is not None:
625 started = datetime.strptime(started, util.ISO8601)
626 628 result = {
627 629 'result_header' : rheader,
628 630 'result_content': msg['content'],
@@ -644,9 +646,9 b' class Hub(LoggingFactory):'
644 646 client_id = idents[0]
645 647
646 648 try:
647 msg = self.session.unpack_message(msg, content=False)
648 except:
649 self.log.error("task::client %r sent invalid task message: %s"%(
649 msg = self.session.unpack_message(msg)
650 except Exception:
651 self.log.error("task::client %r sent invalid task message: %r"%(
650 652 client_id, msg), exc_info=True)
651 653 return
652 654 record = init_record(msg)
@@ -678,9 +680,15 b' class Hub(LoggingFactory):'
678 680 self.log.warn("conflicting initial state for record: %r:%r <%r> %r"%(msg_id, rvalue, key, evalue))
679 681 elif evalue and not rvalue:
680 682 record[key] = evalue
681 self.db.update_record(msg_id, record)
683 try:
684 self.db.update_record(msg_id, record)
685 except Exception:
686 self.log.error("DB Error updating record %r"%msg_id, exc_info=True)
682 687 except KeyError:
683 self.db.add_record(msg_id, record)
688 try:
689 self.db.add_record(msg_id, record)
690 except Exception:
691 self.log.error("DB Error adding record %r"%msg_id, exc_info=True)
684 692 except Exception:
685 693 self.log.error("DB Error saving task request %r"%msg_id, exc_info=True)
686 694
@@ -688,11 +696,10 b' class Hub(LoggingFactory):'
688 696 """save the result of a completed task."""
689 697 client_id = idents[0]
690 698 try:
691 msg = self.session.unpack_message(msg, content=False)
692 except:
693 self.log.error("task::invalid task result message send to %r: %s"%(
699 msg = self.session.unpack_message(msg)
700 except Exception:
701 self.log.error("task::invalid task result message send to %r: %r"%(
694 702 client_id, msg), exc_info=True)
695 raise
696 703 return
697 704
698 705 parent = msg['parent_header']
@@ -715,10 +722,8 b' class Hub(LoggingFactory):'
715 722 self.completed[eid].append(msg_id)
716 723 if msg_id in self.tasks[eid]:
717 724 self.tasks[eid].remove(msg_id)
718 completed = datetime.strptime(header['date'], util.ISO8601)
725 completed = header['date']
719 726 started = header.get('started', None)
720 if started is not None:
721 started = datetime.strptime(started, util.ISO8601)
722 727 result = {
723 728 'result_header' : header,
724 729 'result_content': msg['content'],
@@ -734,12 +739,12 b' class Hub(LoggingFactory):'
734 739 self.log.error("DB Error saving task request %r"%msg_id, exc_info=True)
735 740
736 741 else:
737 self.log.debug("task::unknown task %s finished"%msg_id)
742 self.log.debug("task::unknown task %r finished"%msg_id)
738 743
739 744 def save_task_destination(self, idents, msg):
740 745 try:
741 746 msg = self.session.unpack_message(msg, content=True)
742 except:
747 except Exception:
743 748 self.log.error("task::invalid task tracking message", exc_info=True)
744 749 return
745 750 content = msg['content']
@@ -748,11 +753,11 b' class Hub(LoggingFactory):'
748 753 engine_uuid = content['engine_id']
749 754 eid = self.by_ident[engine_uuid]
750 755
751 self.log.info("task::task %s arrived on %s"%(msg_id, eid))
756 self.log.info("task::task %r arrived on %r"%(msg_id, eid))
752 757 if msg_id in self.unassigned:
753 758 self.unassigned.remove(msg_id)
754 759 # else:
755 # self.log.debug("task::task %s not listed as MIA?!"%(msg_id))
760 # self.log.debug("task::task %r not listed as MIA?!"%(msg_id))
756 761
757 762 self.tasks[eid].append(msg_id)
758 763 # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid))
@@ -776,13 +781,13 b' class Hub(LoggingFactory):'
776 781 # print (topics)
777 782 try:
778 783 msg = self.session.unpack_message(msg, content=True)
779 except:
784 except Exception:
780 785 self.log.error("iopub::invalid IOPub message", exc_info=True)
781 786 return
782 787
783 788 parent = msg['parent_header']
784 789 if not parent:
785 self.log.error("iopub::invalid IOPub message: %s"%msg)
790 self.log.error("iopub::invalid IOPub message: %r"%msg)
786 791 return
787 792 msg_id = parent['msg_id']
788 793 msg_type = msg['msg_type']
@@ -822,7 +827,7 b' class Hub(LoggingFactory):'
822 827
823 828 def connection_request(self, client_id, msg):
824 829 """Reply with connection addresses for clients."""
825 self.log.info("client::client %s connected"%client_id)
830 self.log.info("client::client %r connected"%client_id)
826 831 content = dict(status='ok')
827 832 content.update(self.client_info)
828 833 jsonable = {}
@@ -894,7 +899,7 b' class Hub(LoggingFactory):'
894 899 dc.start()
895 900 self.incoming_registrations[heart] = (eid,queue,reg[0],dc)
896 901 else:
897 self.log.error("registration::registration %i failed: %s"%(eid, content['evalue']))
902 self.log.error("registration::registration %i failed: %r"%(eid, content['evalue']))
898 903 return eid
899 904
900 905 def unregister_engine(self, ident, msg):
@@ -902,9 +907,9 b' class Hub(LoggingFactory):'
902 907 try:
903 908 eid = msg['content']['id']
904 909 except:
905 self.log.error("registration::bad engine id for unregistration: %s"%ident, exc_info=True)
910 self.log.error("registration::bad engine id for unregistration: %r"%ident, exc_info=True)
906 911 return
907 self.log.info("registration::unregister_engine(%s)"%eid)
912 self.log.info("registration::unregister_engine(%r)"%eid)
908 913 # print (eid)
909 914 uuid = self.keytable[eid]
910 915 content=dict(id=eid, queue=uuid)
@@ -1124,7 +1129,7 b' class Hub(LoggingFactory):'
1124 1129 elif len(records) < len(msg_ids):
1125 1130 missing = [ m for m in msg_ids if m not in found_ids ]
1126 1131 try:
1127 raise KeyError("No such msg(s): %s"%missing)
1132 raise KeyError("No such msg(s): %r"%missing)
1128 1133 except KeyError:
1129 1134 return finish(error.wrap_exception())
1130 1135 elif invalid_ids:
@@ -1135,9 +1140,10 b' class Hub(LoggingFactory):'
1135 1140 return finish(error.wrap_exception())
1136 1141
1137 1142 # clear the existing records
1143 now = datetime.now()
1138 1144 rec = empty_record()
1139 1145 map(rec.pop, ['msg_id', 'header', 'content', 'buffers', 'submitted'])
1140 rec['resubmitted'] = datetime.now()
1146 rec['resubmitted'] = now
1141 1147 rec['queue'] = 'task'
1142 1148 rec['client_uuid'] = client_id[0]
1143 1149 try:
@@ -1151,6 +1157,8 b' class Hub(LoggingFactory):'
1151 1157 # send the messages
1152 1158 for rec in records:
1153 1159 header = rec['header']
1160 # include resubmitted in header to prevent digest collision
1161 header['resubmitted'] = now
1154 1162 msg = self.session.msg(header['msg_type'])
1155 1163 msg['content'] = rec['content']
1156 1164 msg['header'] = header
@@ -1246,10 +1254,8 b' class Hub(LoggingFactory):'
1246 1254 content = msg['content']
1247 1255 query = content.get('query', {})
1248 1256 keys = content.get('keys', None)
1249 query = util.extract_dates(query)
1250 1257 buffers = []
1251 1258 empty = list()
1252
1253 1259 try:
1254 1260 records = self.db.find_records(query, keys)
1255 1261 except Exception as e:
@@ -1,6 +1,11 b''
1 """A TaskRecord backend using mongodb"""
1 """A TaskRecord backend using mongodb
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
5 10 # Distributed under the terms of the BSD License. The full license is in
6 11 # the file COPYING, distributed as part of this software.
@@ -9,7 +14,7 b''
9 14 from pymongo import Connection
10 15 from pymongo.binary import Binary
11 16
12 from IPython.utils.traitlets import Dict, List, CUnicode, CStr, Instance
17 from IPython.utils.traitlets import Dict, List, Unicode, Instance
13 18
14 19 from .dictdb import BaseDB
15 20
@@ -20,9 +25,20 b' from .dictdb import BaseDB'
20 25 class MongoDB(BaseDB):
21 26 """MongoDB TaskRecord backend."""
22 27
23 connection_args = List(config=True) # args passed to pymongo.Connection
24 connection_kwargs = Dict(config=True) # kwargs passed to pymongo.Connection
25 database = CUnicode(config=True) # name of the mongodb database
28 connection_args = List(config=True,
29 help="""Positional arguments to be passed to pymongo.Connection. Only
30 necessary if the default mongodb configuration does not point to your
31 mongod instance.""")
32 connection_kwargs = Dict(config=True,
33 help="""Keyword arguments to be passed to pymongo.Connection. Only
34 necessary if the default mongodb configuration does not point to your
35 mongod instance."""
36 )
37 database = Unicode(config=True,
38 help="""The MongoDB database name to use for storing tasks for this session. If unspecified,
39 a new database will be created with the Hub's IDENT. Specifying the database will result
40 in tasks from previous sessions being available via Clients' db_query and
41 get_result methods.""")
26 42
27 43 _connection = Instance(Connection) # pymongo connection
28 44
@@ -3,6 +3,10 b''
3 3 The Pure ZMQ scheduler does not allow routing schemes other than LRU,
4 4 nor does it check msg_id DAG dependencies. For those, a slightly slower
5 5 Python Scheduler exists.
6
7 Authors:
8
9 * Min RK
6 10 """
7 11 #-----------------------------------------------------------------------------
8 12 # Copyright (C) 2010-2011 The IPython Development Team
@@ -35,7 +39,7 b' from zmq.eventloop import ioloop, zmqstream'
35 39 # local imports
36 40 from IPython.external.decorator import decorator
37 41 from IPython.config.loader import Config
38 from IPython.utils.traitlets import Instance, Dict, List, Set, Int
42 from IPython.utils.traitlets import Instance, Dict, List, Set, Int, Str, Enum
39 43
40 44 from IPython.parallel import error
41 45 from IPython.parallel.factory import SessionFactory
@@ -126,10 +130,24 b' class TaskScheduler(SessionFactory):'
126 130
127 131 """
128 132
129 hwm = Int(0, config=True) # limit number of outstanding tasks
133 hwm = Int(0, config=True, shortname='hwm',
134 help="""specify the High Water Mark (HWM) for the downstream
135 socket in the Task scheduler. This is the maximum number
136 of allowed outstanding tasks on each engine."""
137 )
138 scheme_name = Enum(('leastload', 'pure', 'lru', 'plainrandom', 'weighted', 'twobin'),
139 'leastload', config=True, shortname='scheme', allow_none=False,
140 help="""select the task scheduler scheme [default: Python LRU]
141 Options are: 'pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'"""
142 )
143 def _scheme_name_changed(self, old, new):
144 self.log.debug("Using scheme %r"%new)
145 self.scheme = globals()[new]
130 146
131 147 # input arguments:
132 scheme = Instance(FunctionType, default=leastload) # function for determining the destination
148 scheme = Instance(FunctionType) # function for determining the destination
149 def _scheme_default(self):
150 return leastload
133 151 client_stream = Instance(zmqstream.ZMQStream) # client-facing stream
134 152 engine_stream = Instance(zmqstream.ZMQStream) # engine-facing stream
135 153 notifier_stream = Instance(zmqstream.ZMQStream) # hub-facing sub stream
@@ -165,7 +183,7 b' class TaskScheduler(SessionFactory):'
165 183 self.notifier_stream.on_recv(self.dispatch_notification)
166 184 self.auditor = ioloop.PeriodicCallback(self.audit_timeouts, 2e3, self.loop) # 1 Hz
167 185 self.auditor.start()
168 self.log.info("Scheduler started...%r"%self)
186 self.log.info("Scheduler started [%s]"%self.scheme_name)
169 187
170 188 def resume_receiving(self):
171 189 """Resume accepting jobs."""
@@ -182,17 +200,27 b' class TaskScheduler(SessionFactory):'
182 200
183 201 def dispatch_notification(self, msg):
184 202 """dispatch register/unregister events."""
185 idents,msg = self.session.feed_identities(msg)
186 msg = self.session.unpack_message(msg)
203 try:
204 idents,msg = self.session.feed_identities(msg)
205 except ValueError:
206 self.log.warn("task::Invalid Message: %r"%msg)
207 return
208 try:
209 msg = self.session.unpack_message(msg)
210 except ValueError:
211 self.log.warn("task::Unauthorized message from: %r"%idents)
212 return
213
187 214 msg_type = msg['msg_type']
215
188 216 handler = self._notification_handlers.get(msg_type, None)
189 217 if handler is None:
190 raise Exception("Unhandled message type: %s"%msg_type)
218 self.log.error("Unhandled message type: %r"%msg_type)
191 219 else:
192 220 try:
193 221 handler(str(msg['content']['queue']))
194 222 except KeyError:
195 self.log.error("task::Invalid notification msg: %s"%msg)
223 self.log.error("task::Invalid notification msg: %r"%msg)
196 224
197 225 @logged
198 226 def _register_engine(self, uid):
@@ -247,10 +275,8 b' class TaskScheduler(SessionFactory):'
247 275 continue
248 276
249 277 raw_msg = lost[msg_id][0]
250
251 278 idents,msg = self.session.feed_identities(raw_msg, copy=False)
252 msg = self.session.unpack_message(msg, copy=False, content=False)
253 parent = msg['header']
279 parent = self.session.unpack(msg[1].bytes)
254 280 idents = [engine, idents[0]]
255 281
256 282 # build fake error reply
@@ -280,9 +306,10 b' class TaskScheduler(SessionFactory):'
280 306 idents, msg = self.session.feed_identities(raw_msg, copy=False)
281 307 msg = self.session.unpack_message(msg, content=False, copy=False)
282 308 except Exception:
283 self.log.error("task::Invaid task: %s"%raw_msg, exc_info=True)
309 self.log.error("task::Invaid task msg: %r"%raw_msg, exc_info=True)
284 310 return
285 311
312
286 313 # send to monitor
287 314 self.mon_stream.send_multipart(['intask']+raw_msg, copy=False)
288 315
@@ -363,8 +390,7 b' class TaskScheduler(SessionFactory):'
363 390
364 391 # FIXME: unpacking a message I've already unpacked, but didn't save:
365 392 idents,msg = self.session.feed_identities(raw_msg, copy=False)
366 msg = self.session.unpack_message(msg, copy=False, content=False)
367 header = msg['header']
393 header = self.session.unpack(msg[1].bytes)
368 394
369 395 try:
370 396 raise why()
@@ -483,7 +509,7 b' class TaskScheduler(SessionFactory):'
483 509 else:
484 510 self.finish_job(idx)
485 511 except Exception:
486 self.log.error("task::Invaid result: %s"%raw_msg, exc_info=True)
512 self.log.error("task::Invaid result: %r"%raw_msg, exc_info=True)
487 513 return
488 514
489 515 header = msg['header']
@@ -590,7 +616,8 b' class TaskScheduler(SessionFactory):'
590 616 for msg_id in jobs:
591 617 raw_msg, targets, after, follow, timeout = self.depending[msg_id]
592 618
593 if after.unreachable(self.all_completed, self.all_failed) or follow.unreachable(self.all_completed, self.all_failed):
619 if after.unreachable(self.all_completed, self.all_failed)\
620 or follow.unreachable(self.all_completed, self.all_failed):
594 621 self.fail_unreachable(msg_id)
595 622
596 623 elif after.check(self.all_completed, self.all_failed): # time deps met, maybe run
@@ -621,9 +648,9 b' class TaskScheduler(SessionFactory):'
621 648
622 649
623 650
624 def launch_scheduler(in_addr, out_addr, mon_addr, not_addr, config=None,logname='ZMQ',
625 log_addr=None, loglevel=logging.DEBUG, scheme='lru',
626 identity=b'task'):
651 def launch_scheduler(in_addr, out_addr, mon_addr, not_addr, config=None,
652 logname='root', log_url=None, loglevel=logging.DEBUG,
653 identity=b'task'):
627 654 from zmq.eventloop import ioloop
628 655 from zmq.eventloop.zmqstream import ZMQStream
629 656
@@ -646,16 +673,16 b' def launch_scheduler(in_addr, out_addr, mon_addr, not_addr, config=None,logname='
646 673 nots.setsockopt(zmq.SUBSCRIBE, '')
647 674 nots.connect(not_addr)
648 675
649 scheme = globals().get(scheme, None)
650 # setup logging
651 if log_addr:
652 connect_logger(logname, ctx, log_addr, root="scheduler", loglevel=loglevel)
676 # setup logging. Note that these will not work in-process, because they clobber
677 # existing loggers.
678 if log_url:
679 log = connect_logger(logname, ctx, log_url, root="scheduler", loglevel=loglevel)
653 680 else:
654 local_logger(logname, loglevel)
681 log = local_logger(logname, loglevel)
655 682
656 683 scheduler = TaskScheduler(client_stream=ins, engine_stream=outs,
657 684 mon_stream=mons, notifier_stream=nots,
658 scheme=scheme, loop=loop, logname=logname,
685 loop=loop, log=log,
659 686 config=config)
660 687 scheduler.start()
661 688 try:
@@ -1,4 +1,9 b''
1 """A TaskRecord backend using sqlite3"""
1 """A TaskRecord backend using sqlite3
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2011 The IPython Development Team
4 9 #
@@ -15,9 +20,9 b' import sqlite3'
15 20
16 21 from zmq.eventloop import ioloop
17 22
18 from IPython.utils.traitlets import CUnicode, CStr, Instance, List
23 from IPython.utils.traitlets import Unicode, Instance, List, Dict
19 24 from .dictdb import BaseDB
20 from IPython.parallel.util import ISO8601
25 from IPython.utils.jsonutil import date_default, extract_dates, squash_dates
21 26
22 27 #-----------------------------------------------------------------------------
23 28 # SQLite operators, adapters, and converters
@@ -42,23 +47,14 b' null_operators = {'
42 47 '!=' : "IS NOT NULL",
43 48 }
44 49
45 def _adapt_datetime(dt):
46 return dt.strftime(ISO8601)
47
48 def _convert_datetime(ds):
49 if ds is None:
50 return ds
51 else:
52 return datetime.strptime(ds, ISO8601)
53
54 50 def _adapt_dict(d):
55 return json.dumps(d)
51 return json.dumps(d, default=date_default)
56 52
57 53 def _convert_dict(ds):
58 54 if ds is None:
59 55 return ds
60 56 else:
61 return json.loads(ds)
57 return extract_dates(json.loads(ds))
62 58
63 59 def _adapt_bufs(bufs):
64 60 # this is *horrible*
@@ -83,11 +79,19 b' def _convert_bufs(bs):'
83 79 class SQLiteDB(BaseDB):
84 80 """SQLite3 TaskRecord backend."""
85 81
86 filename = CUnicode('tasks.db', config=True)
87 location = CUnicode('', config=True)
88 table = CUnicode("", config=True)
82 filename = Unicode('tasks.db', config=True,
83 help="""The filename of the sqlite task database. [default: 'tasks.db']""")
84 location = Unicode('', config=True,
85 help="""The directory containing the sqlite task database. The default
86 is to use the cluster_dir location.""")
87 table = Unicode("", config=True,
88 help="""The SQLite Table to use for storing tasks for this session. If unspecified,
89 a new table will be created with the Hub's IDENT. Specifying the table will result
90 in tasks from previous sessions being available via Clients' db_query and
91 get_result methods.""")
89 92
90 93 _db = Instance('sqlite3.Connection')
94 # the ordered list of column names
91 95 _keys = List(['msg_id' ,
92 96 'header' ,
93 97 'content',
@@ -108,6 +112,27 b' class SQLiteDB(BaseDB):'
108 112 'stdout',
109 113 'stderr',
110 114 ])
115 # sqlite datatypes for checking that db is current format
116 _types = Dict({'msg_id' : 'text' ,
117 'header' : 'dict text',
118 'content' : 'dict text',
119 'buffers' : 'bufs blob',
120 'submitted' : 'timestamp',
121 'client_uuid' : 'text',
122 'engine_uuid' : 'text',
123 'started' : 'timestamp',
124 'completed' : 'timestamp',
125 'resubmitted' : 'timestamp',
126 'result_header' : 'dict text',
127 'result_content' : 'dict text',
128 'result_buffers' : 'bufs blob',
129 'queue' : 'text',
130 'pyin' : 'text',
131 'pyout' : 'text',
132 'pyerr' : 'text',
133 'stdout' : 'text',
134 'stderr' : 'text',
135 })
111 136
112 137 def __init__(self, **kwargs):
113 138 super(SQLiteDB, self).__init__(**kwargs)
@@ -115,10 +140,16 b' class SQLiteDB(BaseDB):'
115 140 # use session, and prefix _, since starting with # is illegal
116 141 self.table = '_'+self.session.replace('-','_')
117 142 if not self.location:
118 if hasattr(self.config.Global, 'cluster_dir'):
119 self.location = self.config.Global.cluster_dir
143 # get current profile
144 from IPython.core.application import BaseIPythonApplication
145 if BaseIPythonApplication.initialized():
146 app = BaseIPythonApplication.instance()
147 if app.profile_dir is not None:
148 self.location = app.profile_dir.location
149 else:
150 self.location = u'.'
120 151 else:
121 self.location = '.'
152 self.location = u'.'
122 153 self._init_db()
123 154
124 155 # register db commit as 2s periodic callback
@@ -136,11 +167,36 b' class SQLiteDB(BaseDB):'
136 167 d[key] = None
137 168 return d
138 169
170 def _check_table(self):
171 """Ensure that an incorrect table doesn't exist
172
173 If a bad (old) table does exist, return False
174 """
175 cursor = self._db.execute("PRAGMA table_info(%s)"%self.table)
176 lines = cursor.fetchall()
177 if not lines:
178 # table does not exist
179 return True
180 types = {}
181 keys = []
182 for line in lines:
183 keys.append(line[1])
184 types[line[1]] = line[2]
185 if self._keys != keys:
186 # key mismatch
187 self.log.warn('keys mismatch')
188 return False
189 for key in self._keys:
190 if types[key] != self._types[key]:
191 self.log.warn(
192 'type mismatch: %s: %s != %s'%(key,types[key],self._types[key])
193 )
194 return False
195 return True
196
139 197 def _init_db(self):
140 198 """Connect to the database and get new session number."""
141 199 # register adapters
142 sqlite3.register_adapter(datetime, _adapt_datetime)
143 sqlite3.register_converter('datetime', _convert_datetime)
144 200 sqlite3.register_adapter(dict, _adapt_dict)
145 201 sqlite3.register_converter('dict', _convert_dict)
146 202 sqlite3.register_adapter(list, _adapt_bufs)
@@ -151,18 +207,27 b' class SQLiteDB(BaseDB):'
151 207 # isolation_level = None)#,
152 208 cached_statements=64)
153 209 # print dir(self._db)
210 first_table = self.table
211 i=0
212 while not self._check_table():
213 i+=1
214 self.table = first_table+'_%i'%i
215 self.log.warn(
216 "Table %s exists and doesn't match db format, trying %s"%
217 (first_table,self.table)
218 )
154 219
155 220 self._db.execute("""CREATE TABLE IF NOT EXISTS %s
156 221 (msg_id text PRIMARY KEY,
157 222 header dict text,
158 223 content dict text,
159 224 buffers bufs blob,
160 submitted datetime text,
225 submitted timestamp,
161 226 client_uuid text,
162 227 engine_uuid text,
163 started datetime text,
164 completed datetime text,
165 resubmitted datetime text,
228 started timestamp,
229 completed timestamp,
230 resubmitted timestamp,
166 231 result_header dict text,
167 232 result_content dict text,
168 233 result_buffers bufs blob,
@@ -2,6 +2,10 b''
2 2 """A simple engine that talks to a controller over 0MQ.
3 3 it handles registration, etc. and launches a kernel
4 4 connected to the Controller's Schedulers.
5
6 Authors:
7
8 * Min RK
5 9 """
6 10 #-----------------------------------------------------------------------------
7 11 # Copyright (C) 2010-2011 The IPython Development Team
@@ -19,27 +23,37 b' import zmq'
19 23 from zmq.eventloop import ioloop, zmqstream
20 24
21 25 # internal
22 from IPython.utils.traitlets import Instance, Str, Dict, Int, Type, CFloat
26 from IPython.utils.traitlets import Instance, Dict, Int, Type, CFloat, Unicode
23 27 # from IPython.utils.localinterfaces import LOCALHOST
24 28
25 29 from IPython.parallel.controller.heartmonitor import Heart
26 30 from IPython.parallel.factory import RegistrationFactory
27 from IPython.parallel.streamsession import Message
28 31 from IPython.parallel.util import disambiguate_url
29 32
33 from IPython.zmq.session import Message
34
30 35 from .streamkernel import Kernel
31 36
32 37 class EngineFactory(RegistrationFactory):
33 38 """IPython engine"""
34 39
35 40 # configurables:
36 user_ns=Dict(config=True)
37 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True)
38 display_hook_factory=Type('IPython.zmq.displayhook.DisplayHook', config=True)
39 location=Str(config=True)
40 timeout=CFloat(2,config=True)
41 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True,
42 help="""The OutStream for handling stdout/err.
43 Typically 'IPython.zmq.iostream.OutStream'""")
44 display_hook_factory=Type('IPython.zmq.displayhook.DisplayHook', config=True,
45 help="""The class for handling displayhook.
46 Typically 'IPython.zmq.displayhook.DisplayHook'""")
47 location=Unicode(config=True,
48 help="""The location (an IP address) of the controller. This is
49 used for disambiguating URLs, to determine whether
50 loopback should be used to connect or the public address.""")
51 timeout=CFloat(2,config=True,
52 help="""The time (in seconds) to wait for the Controller to respond
53 to registration requests before giving up.""")
41 54
42 55 # not configurable:
56 user_ns=Dict()
43 57 id=Int(allow_none=True)
44 58 registrar=Instance('zmq.eventloop.zmqstream.ZMQStream')
45 59 kernel=Instance(Kernel)
@@ -47,6 +61,7 b' class EngineFactory(RegistrationFactory):'
47 61
48 62 def __init__(self, **kwargs):
49 63 super(EngineFactory, self).__init__(**kwargs)
64 self.ident = self.session.session
50 65 ctx = self.context
51 66
52 67 reg = ctx.socket(zmq.XREQ)
@@ -127,11 +142,10 b' class EngineFactory(RegistrationFactory):'
127 142
128 143 self.kernel = Kernel(config=self.config, int_id=self.id, ident=self.ident, session=self.session,
129 144 control_stream=control_stream, shell_streams=shell_streams, iopub_stream=iopub_stream,
130 loop=loop, user_ns = self.user_ns, logname=self.log.name)
145 loop=loop, user_ns = self.user_ns, log=self.log)
131 146 self.kernel.start()
132 147 hb_addrs = [ disambiguate_url(addr, self.location) for addr in hb_addrs ]
133 148 heart = Heart(*map(str, hb_addrs), heart_id=identity)
134 # ioloop.DelayedCallback(heart.start, 1000, self.loop).start()
135 149 heart.start()
136 150
137 151
@@ -143,7 +157,7 b' class EngineFactory(RegistrationFactory):'
143 157
144 158
145 159 def abort(self):
146 self.log.fatal("Registration timed out")
160 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
147 161 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
148 162 time.sleep(1)
149 163 sys.exit(255)
@@ -1,4 +1,9 b''
1 """KernelStarter class that intercepts Control Queue messages, and handles process management."""
1 """KernelStarter class that intercepts Control Queue messages, and handles process management.
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
@@ -8,7 +13,7 b''
8 13
9 14 from zmq.eventloop import ioloop
10 15
11 from IPython.parallel.streamsession import StreamSession
16 from IPython.zmq.session import Session
12 17
13 18 class KernelStarter(object):
14 19 """Object for resetting/killing the Kernel."""
@@ -213,7 +218,7 b' def make_starter(up_addr, down_addr, *args, **kwargs):'
213 218 """entry point function for launching a kernelstarter in a subprocess"""
214 219 loop = ioloop.IOLoop.instance()
215 220 ctx = zmq.Context()
216 session = StreamSession()
221 session = Session()
217 222 upstream = zmqstream.ZMQStream(ctx.socket(zmq.XREQ),loop)
218 223 upstream.connect(up_addr)
219 224 downstream = zmqstream.ZMQStream(ctx.socket(zmq.XREQ),loop)
@@ -1,6 +1,13 b''
1 1 #!/usr/bin/env python
2 2 """
3 3 Kernel adapted from kernel.py to use ZMQ Streams
4
5 Authors:
6
7 * Min RK
8 * Brian Granger
9 * Fernando Perez
10 * Evan Patterson
4 11 """
5 12 #-----------------------------------------------------------------------------
6 13 # Copyright (C) 2010-2011 The IPython Development Team
@@ -28,12 +35,12 b' import zmq'
28 35 from zmq.eventloop import ioloop, zmqstream
29 36
30 37 # Local imports.
31 from IPython.utils.traitlets import Instance, List, Int, Dict, Set, Str
38 from IPython.utils.traitlets import Instance, List, Int, Dict, Set, Unicode
32 39 from IPython.zmq.completer import KernelCompleter
33 40
34 41 from IPython.parallel.error import wrap_exception
35 42 from IPython.parallel.factory import SessionFactory
36 from IPython.parallel.util import serialize_object, unpack_apply_message, ISO8601
43 from IPython.parallel.util import serialize_object, unpack_apply_message
37 44
38 45 def printer(*args):
39 46 pprint(args, stream=sys.__stdout__)
@@ -42,7 +49,7 b' def printer(*args):'
42 49 class _Passer(zmqstream.ZMQStream):
43 50 """Empty class that implements `send()` that does nothing.
44 51
45 Subclass ZMQStream for StreamSession typechecking
52 Subclass ZMQStream for Session typechecking
46 53
47 54 """
48 55 def __init__(self, *args, **kwargs):
@@ -64,9 +71,11 b' class Kernel(SessionFactory):'
64 71 #---------------------------------------------------------------------------
65 72
66 73 # kwargs:
67 int_id = Int(-1, config=True)
68 user_ns = Dict(config=True)
69 exec_lines = List(config=True)
74 exec_lines = List(Unicode, config=True,
75 help="List of lines to execute")
76
77 int_id = Int(-1)
78 user_ns = Dict(config=True, help="""Set the user's namespace of the Kernel""")
70 79
71 80 control_stream = Instance(zmqstream.ZMQStream)
72 81 task_stream = Instance(zmqstream.ZMQStream)
@@ -129,21 +138,10 b' class Kernel(SessionFactory):'
129 138
130 139 def abort_queue(self, stream):
131 140 while True:
132 try:
133 msg = self.session.recv(stream, zmq.NOBLOCK,content=True)
134 except zmq.ZMQError as e:
135 if e.errno == zmq.EAGAIN:
136 break
137 else:
138 return
139 else:
140 if msg is None:
141 return
142 else:
143 idents,msg = msg
141 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
142 if msg is None:
143 return
144 144
145 # assert self.reply_socketly_socket.rcvmore(), "Unexpected missing message part."
146 # msg = self.reply_socket.recv_json()
147 145 self.log.info("Aborting:")
148 146 self.log.info(str(msg))
149 147 msg_type = msg['msg_type']
@@ -250,7 +248,7 b' class Kernel(SessionFactory):'
250 248 return
251 249 self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent,
252 250 ident='%s.pyin'%self.prefix)
253 started = datetime.now().strftime(ISO8601)
251 started = datetime.now()
254 252 try:
255 253 comp_code = self.compiler(code, '<zmq-kernel>')
256 254 # allow for not overriding displayhook
@@ -300,7 +298,7 b' class Kernel(SessionFactory):'
300 298 # self.iopub_stream.send(pyin_msg)
301 299 # self.session.send(self.iopub_stream, u'pyin', {u'code':code},parent=parent)
302 300 sub = {'dependencies_met' : True, 'engine' : self.ident,
303 'started': datetime.now().strftime(ISO8601)}
301 'started': datetime.now()}
304 302 try:
305 303 # allow for not overriding displayhook
306 304 if hasattr(sys.displayhook, 'set_parent'):
@@ -1,6 +1,12 b''
1 1 # encoding: utf-8
2 2
3 """Classes and functions for kernel related errors and exceptions."""
3 """Classes and functions for kernel related errors and exceptions.
4
5 Authors:
6
7 * Brian Granger
8 * Min RK
9 """
4 10 from __future__ import print_function
5 11
6 12 import sys
@@ -12,7 +18,7 b' __docformat__ = "restructuredtext en"'
12 18 __test__ = {}
13 19
14 20 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
21 # Copyright (C) 2008-2011 The IPython Development Team
16 22 #
17 23 # Distributed under the terms of the BSD License. The full license is in
18 24 # the file COPYING, distributed as part of this software.
@@ -1,7 +1,12 b''
1 """Base config factories."""
1 """Base config factories.
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2009 The IPython Development Team
9 # Copyright (C) 2010-2011 The IPython Development Team
5 10 #
6 11 # Distributed under the terms of the BSD License. The full license is in
7 12 # the file COPYING, distributed as part of this software.
@@ -14,75 +19,38 b''
14 19
15 20 import logging
16 21 import os
17 import uuid
18 22
23 import zmq
19 24 from zmq.eventloop.ioloop import IOLoop
20 25
21 26 from IPython.config.configurable import Configurable
22 from IPython.utils.importstring import import_item
23 from IPython.utils.traitlets import Str,Int,Instance, CUnicode, CStr
27 from IPython.utils.traitlets import Int, Instance, Unicode
24 28
25 import IPython.parallel.streamsession as ss
26 29 from IPython.parallel.util import select_random_ports
30 from IPython.zmq.session import Session, SessionFactory
27 31
28 32 #-----------------------------------------------------------------------------
29 33 # Classes
30 34 #-----------------------------------------------------------------------------
31 class LoggingFactory(Configurable):
32 """A most basic class, that has a `log` (type:`Logger`) attribute, set via a `logname` Trait."""
33 log = Instance('logging.Logger', ('ZMQ', logging.WARN))
34 logname = CUnicode('ZMQ')
35 def _logname_changed(self, name, old, new):
36 self.log = logging.getLogger(new)
37
38 35
39 class SessionFactory(LoggingFactory):
40 """The Base factory from which every factory in IPython.parallel inherits"""
41
42 packer = Str('',config=True)
43 unpacker = Str('',config=True)
44 ident = CStr('',config=True)
45 def _ident_default(self):
46 return str(uuid.uuid4())
47 username = CUnicode(os.environ.get('USER','username'),config=True)
48 exec_key = CUnicode('',config=True)
49 # not configurable:
50 context = Instance('zmq.Context', (), {})
51 session = Instance('IPython.parallel.streamsession.StreamSession')
52 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
53 def _loop_default(self):
54 return IOLoop.instance()
55
56
57 def __init__(self, **kwargs):
58 super(SessionFactory, self).__init__(**kwargs)
59 exec_key = self.exec_key or None
60 # set the packers:
61 if not self.packer:
62 packer_f = unpacker_f = None
63 elif self.packer.lower() == 'json':
64 packer_f = ss.json_packer
65 unpacker_f = ss.json_unpacker
66 elif self.packer.lower() == 'pickle':
67 packer_f = ss.pickle_packer
68 unpacker_f = ss.pickle_unpacker
69 else:
70 packer_f = import_item(self.packer)
71 unpacker_f = import_item(self.unpacker)
72
73 # construct the session
74 self.session = ss.StreamSession(self.username, self.ident, packer=packer_f, unpacker=unpacker_f, key=exec_key)
75
76 36
77 37 class RegistrationFactory(SessionFactory):
78 38 """The Base Configurable for objects that involve registration."""
79 39
80 url = Str('', config=True) # url takes precedence over ip,regport,transport
81 transport = Str('tcp', config=True)
82 ip = Str('127.0.0.1', config=True)
83 regport = Instance(int, config=True)
40 url = Unicode('', config=True,
41 help="""The 0MQ url used for registration. This sets transport, ip, and port
42 in one variable. For example: url='tcp://127.0.0.1:12345' or
43 url='epgm://*:90210'""") # url takes precedence over ip,regport,transport
44 transport = Unicode('tcp', config=True,
45 help="""The 0MQ transport for communications. This will likely be
46 the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""")
47 ip = Unicode('127.0.0.1', config=True,
48 help="""The IP address for registration. This is generally either
49 '127.0.0.1' for loopback only or '*' for all interfaces.
50 [default: '127.0.0.1']""")
51 regport = Int(config=True,
52 help="""The port on which the Hub listens for registration.""")
84 53 def _regport_default(self):
85 # return 10101
86 54 return select_random_ports(1)[0]
87 55
88 56 def __init__(self, **kwargs):
@@ -107,46 +75,3 b' class RegistrationFactory(SessionFactory):'
107 75 self.ip = iface[0]
108 76 if iface[1]:
109 77 self.regport = int(iface[1])
110
111 #-----------------------------------------------------------------------------
112 # argparse argument extenders
113 #-----------------------------------------------------------------------------
114
115
116 def add_session_arguments(parser):
117 paa = parser.add_argument
118 paa('--ident',
119 type=str, dest='SessionFactory.ident',
120 help='set the ZMQ and session identity [default: random uuid]',
121 metavar='identity')
122 # paa('--execkey',
123 # type=str, dest='SessionFactory.exec_key',
124 # help='path to a file containing an execution key.',
125 # metavar='execkey')
126 paa('--packer',
127 type=str, dest='SessionFactory.packer',
128 help='method to serialize messages: {json,pickle} [default: json]',
129 metavar='packer')
130 paa('--unpacker',
131 type=str, dest='SessionFactory.unpacker',
132 help='inverse function of `packer`. Only necessary when using something other than json|pickle',
133 metavar='packer')
134
135 def add_registration_arguments(parser):
136 paa = parser.add_argument
137 paa('--ip',
138 type=str, dest='RegistrationFactory.ip',
139 help="The IP used for registration [default: localhost]",
140 metavar='ip')
141 paa('--transport',
142 type=str, dest='RegistrationFactory.transport',
143 help="The ZeroMQ transport used for registration [default: tcp]",
144 metavar='transport')
145 paa('--url',
146 type=str, dest='RegistrationFactory.url',
147 help='set transport,ip,regport in one go, e.g. tcp://127.0.0.1:10101',
148 metavar='url')
149 paa('--regport',
150 type=int, dest='RegistrationFactory.regport',
151 help="The port used for registration [default: 10101]",
152 metavar='ip')
@@ -48,10 +48,10 b' class TestProcessLauncher(LocalProcessLauncher):'
48 48 def setup():
49 49 cp = TestProcessLauncher()
50 50 cp.cmd_and_args = ipcontroller_cmd_argv + \
51 ['--profile', 'iptest', '--log-level', '99', '-r']
51 ['profile=iptest', 'log_level=50', '--reuse']
52 52 cp.start()
53 53 launchers.append(cp)
54 cluster_dir = os.path.join(get_ipython_dir(), 'cluster_iptest')
54 cluster_dir = os.path.join(get_ipython_dir(), 'profile_iptest')
55 55 engine_json = os.path.join(cluster_dir, 'security', 'ipcontroller-engine.json')
56 56 client_json = os.path.join(cluster_dir, 'security', 'ipcontroller-client.json')
57 57 tic = time.time()
@@ -70,7 +70,7 b" def add_engines(n=1, profile='iptest'):"
70 70 eps = []
71 71 for i in range(n):
72 72 ep = TestProcessLauncher()
73 ep.cmd_and_args = ipengine_cmd_argv + ['--profile', profile, '--log-level', '99']
73 ep.cmd_and_args = ipengine_cmd_argv + ['profile=%s'%profile, 'log_level=50']
74 74 ep.start()
75 75 launchers.append(ep)
76 76 eps.append(ep)
@@ -1,4 +1,9 b''
1 """base class for parallel client tests"""
1 """base class for parallel client tests
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -1,4 +1,9 b''
1 """Tests for asyncresult.py"""
1 """Tests for asyncresult.py
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -1,4 +1,9 b''
1 """Tests for parallel client.py"""
1 """Tests for parallel client.py
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -1,4 +1,9 b''
1 """Tests for db backends"""
1 """Tests for db backends
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -20,18 +25,21 b' from unittest import TestCase'
20 25
21 26 from nose import SkipTest
22 27
23 from IPython.parallel import error, streamsession as ss
28 from IPython.parallel import error
24 29 from IPython.parallel.controller.dictdb import DictDB
25 30 from IPython.parallel.controller.sqlitedb import SQLiteDB
26 31 from IPython.parallel.controller.hub import init_record, empty_record
27 32
33 from IPython.zmq.session import Session
34
35
28 36 #-------------------------------------------------------------------------------
29 37 # TestCases
30 38 #-------------------------------------------------------------------------------
31 39
32 40 class TestDictBackend(TestCase):
33 41 def setUp(self):
34 self.session = ss.StreamSession()
42 self.session = Session()
35 43 self.db = self.create_db()
36 44 self.load_records(16)
37 45
@@ -1,4 +1,9 b''
1 """Tests for dependency.py"""
1 """Tests for dependency.py
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 __docformat__ = "restructuredtext en"
4 9
@@ -1,5 +1,10 b''
1 """test LoadBalancedView objects"""
2 1 # -*- coding: utf-8 -*-
2 """test LoadBalancedView objects
3
4 Authors:
5
6 * Min RK
7 """
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
5 10 #
@@ -1,4 +1,9 b''
1 """Tests for mongodb backend"""
1 """Tests for mongodb backend
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -1,4 +1,9 b''
1 """test serialization with newserialized"""
1 """test serialization with newserialized
2
3 Authors:
4
5 * Min RK
6 """
2 7
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
@@ -1,5 +1,10 b''
1 """test View objects"""
2 1 # -*- coding: utf-8 -*-
2 """test View objects
3
4 Authors:
5
6 * Min RK
7 """
3 8 #-------------------------------------------------------------------------------
4 9 # Copyright (C) 2011 The IPython Development Team
5 10 #
@@ -315,6 +320,7 b' class TestView(ClusterTestCase):'
315 320 sys.stdout = savestdout
316 321 sio.read()
317 322 self.assertTrue('[stdout:%i]'%v.targets in sio.buf)
323 self.assertTrue(sio.buf.rstrip().endswith('10'))
318 324 self.assertRaisesRemote(ZeroDivisionError, ip.magic_px, '1/0')
319 325
320 326 def test_magic_px_nonblocking(self):
@@ -1,4 +1,9 b''
1 """some generic utilities for dealing with classes, urls, and serialization"""
1 """some generic utilities for dealing with classes, urls, and serialization
2
3 Authors:
4
5 * Min RK
6 """
2 7 #-----------------------------------------------------------------------------
3 8 # Copyright (C) 2010-2011 The IPython Development Team
4 9 #
@@ -17,7 +22,6 b' import re'
17 22 import stat
18 23 import socket
19 24 import sys
20 from datetime import datetime
21 25 from signal import signal, SIGINT, SIGABRT, SIGTERM
22 26 try:
23 27 from signal import SIGKILL
@@ -40,10 +44,6 b' from IPython.utils.pickleutil import can, uncan, canSequence, uncanSequence'
40 44 from IPython.utils.newserialized import serialize, unserialize
41 45 from IPython.zmq.log import EnginePUBHandler
42 46
43 # globals
44 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
45 ISO8601_RE=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
46
47 47 #-----------------------------------------------------------------------------
48 48 # Classes
49 49 #-----------------------------------------------------------------------------
@@ -101,18 +101,6 b' class ReverseDict(dict):'
101 101 # Functions
102 102 #-----------------------------------------------------------------------------
103 103
104 def extract_dates(obj):
105 """extract ISO8601 dates from unpacked JSON"""
106 if isinstance(obj, dict):
107 for k,v in obj.iteritems():
108 obj[k] = extract_dates(v)
109 elif isinstance(obj, list):
110 obj = [ extract_dates(o) for o in obj ]
111 elif isinstance(obj, basestring):
112 if ISO8601_RE.match(obj):
113 obj = datetime.strptime(obj, ISO8601)
114 return obj
115
116 104 def validate_url(url):
117 105 """validate a url for zeromq"""
118 106 if not isinstance(url, basestring):
@@ -194,29 +182,6 b' def disambiguate_url(url, location=None):'
194 182
195 183 return "%s://%s:%s"%(proto,ip,port)
196 184
197
198 def rekey(dikt):
199 """Rekey a dict that has been forced to use str keys where there should be
200 ints by json. This belongs in the jsonutil added by fperez."""
201 for k in dikt.iterkeys():
202 if isinstance(k, str):
203 ik=fk=None
204 try:
205 ik = int(k)
206 except ValueError:
207 try:
208 fk = float(k)
209 except ValueError:
210 continue
211 if ik is not None:
212 nk = ik
213 else:
214 nk = fk
215 if nk in dikt:
216 raise KeyError("already have key %r"%nk)
217 dikt[nk] = dikt.pop(k)
218 return dikt
219
220 185 def serialize_object(obj, threshold=64e-6):
221 186 """Serialize an object into a list of sendable buffers.
222 187
@@ -469,6 +434,7 b' def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):'
469 434 handler.setLevel(loglevel)
470 435 logger.addHandler(handler)
471 436 logger.setLevel(loglevel)
437 return logger
472 438
473 439 def local_logger(logname, loglevel=logging.DEBUG):
474 440 loglevel = integer_loglevel(loglevel)
@@ -480,4 +446,5 b' def local_logger(logname, loglevel=logging.DEBUG):'
480 446 handler.setLevel(loglevel)
481 447 logger.addHandler(handler)
482 448 logger.setLevel(loglevel)
449 return logger
483 450
@@ -158,8 +158,8 b' def default_argv():'
158 158
159 159 return ['--quick', # so no config file is loaded
160 160 # Other defaults to minimize side effects on stdout
161 '--colors=NoColor', '--no-term-title','--no-banner',
162 '--autocall=0']
161 'colors=NoColor', '--no-term-title','--no-banner',
162 'autocall=0']
163 163
164 164
165 165 def default_config():
@@ -197,7 +197,10 b' def ipexec(fname, options=None):'
197 197
198 198 # For these subprocess calls, eliminate all prompt printing so we only see
199 199 # output from script execution
200 prompt_opts = ['--prompt-in1=""', '--prompt-in2=""', '--prompt-out=""']
200 prompt_opts = [ 'InteractiveShell.prompt_in1=""',
201 'InteractiveShell.prompt_in2=""',
202 'InteractiveShell.prompt_out=""'
203 ]
201 204 cmdargs = ' '.join(default_argv() + prompt_opts + options)
202 205
203 206 _ip = get_ipython()
@@ -11,12 +11,79 b''
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 import re
14 15 import types
16 from datetime import datetime
17
18 #-----------------------------------------------------------------------------
19 # Globals and constants
20 #-----------------------------------------------------------------------------
21
22 # timestamp formats
23 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
24 ISO8601_PAT=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
15 25
16 26 #-----------------------------------------------------------------------------
17 27 # Classes and functions
18 28 #-----------------------------------------------------------------------------
19 29
30 def rekey(dikt):
31 """Rekey a dict that has been forced to use str keys where there should be
32 ints by json."""
33 for k in dikt.iterkeys():
34 if isinstance(k, basestring):
35 ik=fk=None
36 try:
37 ik = int(k)
38 except ValueError:
39 try:
40 fk = float(k)
41 except ValueError:
42 continue
43 if ik is not None:
44 nk = ik
45 else:
46 nk = fk
47 if nk in dikt:
48 raise KeyError("already have key %r"%nk)
49 dikt[nk] = dikt.pop(k)
50 return dikt
51
52
53 def extract_dates(obj):
54 """extract ISO8601 dates from unpacked JSON"""
55 if isinstance(obj, dict):
56 obj = dict(obj) # don't clobber
57 for k,v in obj.iteritems():
58 obj[k] = extract_dates(v)
59 elif isinstance(obj, (list, tuple)):
60 obj = [ extract_dates(o) for o in obj ]
61 elif isinstance(obj, basestring):
62 if ISO8601_PAT.match(obj):
63 obj = datetime.strptime(obj, ISO8601)
64 return obj
65
66 def squash_dates(obj):
67 """squash datetime objects into ISO8601 strings"""
68 if isinstance(obj, dict):
69 obj = dict(obj) # don't clobber
70 for k,v in obj.iteritems():
71 obj[k] = squash_dates(v)
72 elif isinstance(obj, (list, tuple)):
73 obj = [ squash_dates(o) for o in obj ]
74 elif isinstance(obj, datetime):
75 obj = obj.strftime(ISO8601)
76 return obj
77
78 def date_default(obj):
79 """default function for packing datetime objects in JSON."""
80 if isinstance(obj, datetime):
81 return obj.strftime(ISO8601)
82 else:
83 raise TypeError("%r is not JSON serializable"%obj)
84
85
86
20 87 def json_clean(obj):
21 88 """Clean an object to ensure it's safe to encode in JSON.
22 89
@@ -413,13 +413,20 b' def check_for_old_config(ipython_dir=None):'
413 413 if ipython_dir is None:
414 414 ipython_dir = get_ipython_dir()
415 415
416 old_configs = ['ipy_user_conf.py', 'ipythonrc']
416 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
417 warned = False
417 418 for cfg in old_configs:
418 419 f = os.path.join(ipython_dir, cfg)
419 420 if os.path.exists(f):
420 warn.warn("""Found old IPython config file %r.
421 The IPython configuration system has changed as of 0.11, and this file will be ignored.
422 See http://ipython.github.com/ipython-doc/dev/config for details on the new config system.
423 The current default config file is 'ipython_config.py', where you can suppress these
424 warnings with `Global.ignore_old_config = True`."""%f)
421 warned = True
422 warn.warn("Found old IPython config file %r"%f)
423
424 if warned:
425 warn.warn("""
426 The IPython configuration system has changed as of 0.11, and these files
427 will be ignored. See http://ipython.github.com/ipython-doc/dev/config for
428 details on the new config system. To start configuring IPython, do
429 `ipython profile create`, and edit `ipython_config.py` in
430 <ipython_dir>/profile_default.""")
431
425 432
@@ -19,6 +19,8 b' import __main__'
19 19 import os
20 20 import re
21 21 import shutil
22 import textwrap
23 from string import Formatter
22 24
23 25 from IPython.external.path import path
24 26
@@ -391,15 +393,39 b' def igrep(pat,list):'
391 393 return grep(pat,list,case=0)
392 394
393 395
394 def indent(str,nspaces=4,ntabs=0):
396 def indent(instr,nspaces=4, ntabs=0, flatten=False):
395 397 """Indent a string a given number of spaces or tabstops.
396 398
397 399 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
400
401 Parameters
402 ----------
403
404 instr : basestring
405 The string to be indented.
406 nspaces : int (default: 4)
407 The number of spaces to be indented.
408 ntabs : int (default: 0)
409 The number of tabs to be indented.
410 flatten : bool (default: False)
411 Whether to scrub existing indentation. If True, all lines will be
412 aligned to the same indentation. If False, existing indentation will
413 be strictly increased.
414
415 Returns
416 -------
417
418 str|unicode : string indented by ntabs and nspaces.
419
398 420 """
399 if str is None:
421 if instr is None:
400 422 return
401 423 ind = '\t'*ntabs+' '*nspaces
402 outstr = '%s%s' % (ind,str.replace(os.linesep,os.linesep+ind))
424 if flatten:
425 pat = re.compile(r'^\s*', re.MULTILINE)
426 else:
427 pat = re.compile(r'^', re.MULTILINE)
428 outstr = re.sub(pat, ind, instr)
403 429 if outstr.endswith(os.linesep+ind):
404 430 return outstr[:-len(ind)]
405 431 else:
@@ -495,3 +521,93 b' def format_screen(strng):'
495 521 strng = par_re.sub('',strng)
496 522 return strng
497 523
524 def dedent(text):
525 """Equivalent of textwrap.dedent that ignores unindented first line.
526
527 This means it will still dedent strings like:
528 '''foo
529 is a bar
530 '''
531
532 For use in wrap_paragraphs.
533 """
534
535 if text.startswith('\n'):
536 # text starts with blank line, don't ignore the first line
537 return textwrap.dedent(text)
538
539 # split first line
540 splits = text.split('\n',1)
541 if len(splits) == 1:
542 # only one line
543 return textwrap.dedent(text)
544
545 first, rest = splits
546 # dedent everything but the first line
547 rest = textwrap.dedent(rest)
548 return '\n'.join([first, rest])
549
550 def wrap_paragraphs(text, ncols=80):
551 """Wrap multiple paragraphs to fit a specified width.
552
553 This is equivalent to textwrap.wrap, but with support for multiple
554 paragraphs, as separated by empty lines.
555
556 Returns
557 -------
558
559 list of complete paragraphs, wrapped to fill `ncols` columns.
560 """
561 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
562 text = dedent(text).strip()
563 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
564 out_ps = []
565 indent_re = re.compile(r'\n\s+', re.MULTILINE)
566 for p in paragraphs:
567 # presume indentation that survives dedent is meaningful formatting,
568 # so don't fill unless text is flush.
569 if indent_re.search(p) is None:
570 # wrap paragraph
571 p = textwrap.fill(p, ncols)
572 out_ps.append(p)
573 return out_ps
574
575
576
577 class EvalFormatter(Formatter):
578 """A String Formatter that allows evaluation of simple expressions.
579
580 Any time a format key is not found in the kwargs,
581 it will be tried as an expression in the kwargs namespace.
582
583 This is to be used in templating cases, such as the parallel batch
584 script templates, where simple arithmetic on arguments is useful.
585
586 Examples
587 --------
588
589 In [1]: f = EvalFormatter()
590 In [2]: f.format('{n/4}', n=8)
591 Out[2]: '2'
592
593 In [3]: f.format('{range(3)}')
594 Out[3]: '[0, 1, 2]'
595
596 In [4]: f.format('{3*2}')
597 Out[4]: '6'
598 """
599
600 def get_value(self, key, args, kwargs):
601 if isinstance(key, (int, long)):
602 return args[key]
603 elif key in kwargs:
604 return kwargs[key]
605 else:
606 # evaluate the expression using kwargs as namespace
607 try:
608 return eval(key, kwargs)
609 except Exception:
610 # classify all bad expressions as key errors
611 raise KeyError(key)
612
613
@@ -6,10 +6,10 b''
6 6 #-----------------------------------------------------------------------------
7 7
8 8 #-----------------------------------------------------------------------------
9 # Verify zmq version dependency >= 2.0.10-1
9 # Verify zmq version dependency >= 2.1.4
10 10 #-----------------------------------------------------------------------------
11 11
12 minimum_pyzmq_version = "2.0.10-1"
12 minimum_pyzmq_version = "2.1.4"
13 13
14 14 try:
15 15 import zmq
@@ -21,8 +21,8 b' from Queue import Queue, Empty'
21 21 from IPython.utils import io
22 22 from IPython.utils.traitlets import Type
23 23
24 from .kernelmanager import (KernelManager, SubSocketChannel,
25 XReqSocketChannel, RepSocketChannel, HBSocketChannel)
24 from .kernelmanager import (KernelManager, SubSocketChannel, HBSocketChannel,
25 ShellSocketChannel, StdInSocketChannel)
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Functions and classes
@@ -61,15 +61,15 b' class BlockingSubSocketChannel(SubSocketChannel):'
61 61 return msgs
62 62
63 63
64 class BlockingXReqSocketChannel(XReqSocketChannel):
64 class BlockingShellSocketChannel(ShellSocketChannel):
65 65
66 66 def __init__(self, context, session, address=None):
67 super(BlockingXReqSocketChannel, self).__init__(context, session,
67 super(BlockingShellSocketChannel, self).__init__(context, session,
68 68 address)
69 69 self._in_queue = Queue()
70 70
71 71 def call_handlers(self, msg):
72 #io.rprint('[[XReq]]', msg) # dbg
72 #io.rprint('[[Shell]]', msg) # dbg
73 73 self._in_queue.put(msg)
74 74
75 75 def msg_ready(self):
@@ -94,7 +94,7 b' class BlockingXReqSocketChannel(XReqSocketChannel):'
94 94 return msgs
95 95
96 96
97 class BlockingRepSocketChannel(RepSocketChannel):
97 class BlockingStdInSocketChannel(StdInSocketChannel):
98 98
99 99 def call_handlers(self, msg):
100 100 #io.rprint('[[Rep]]', msg) # dbg
@@ -114,8 +114,8 b' class BlockingHBSocketChannel(HBSocketChannel):'
114 114 class BlockingKernelManager(KernelManager):
115 115
116 116 # The classes to use for the various channels.
117 xreq_channel_class = Type(BlockingXReqSocketChannel)
117 shell_channel_class = Type(BlockingShellSocketChannel)
118 118 sub_channel_class = Type(BlockingSubSocketChannel)
119 rep_channel_class = Type(BlockingRepSocketChannel)
119 stdin_channel_class = Type(BlockingStdInSocketChannel)
120 120 hb_channel_class = Type(BlockingHBSocketChannel)
121 121
@@ -9,159 +9,14 b' import socket'
9 9 from subprocess import Popen, PIPE
10 10 import sys
11 11
12 # System library imports.
13 import zmq
14
15 12 # Local imports.
16 from IPython.core.ultratb import FormattedTB
17 from IPython.external.argparse import ArgumentParser
18 from IPython.utils import io
19 from IPython.utils.localinterfaces import LOCALHOST
20 from displayhook import DisplayHook
21 from heartbeat import Heartbeat
22 from iostream import OutStream
23 from parentpoller import ParentPollerUnix, ParentPollerWindows
24 from session import Session
25
26
27 def bind_port(socket, ip, port):
28 """ Binds the specified ZMQ socket. If the port is zero, a random port is
29 chosen. Returns the port that was bound.
30 """
31 connection = 'tcp://%s' % ip
32 if port <= 0:
33 port = socket.bind_to_random_port(connection)
34 else:
35 connection += ':%i' % port
36 socket.bind(connection)
37 return port
38
39
40 def make_argument_parser():
41 """ Creates an ArgumentParser for the generic arguments supported by all
42 kernel entry points.
43 """
44 parser = ArgumentParser()
45 parser.add_argument('--ip', type=str, default=LOCALHOST,
46 help='set the kernel\'s IP address [default: local]')
47 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
48 help='set the XREP channel port [default: random]')
49 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
50 help='set the PUB channel port [default: random]')
51 parser.add_argument('--req', type=int, metavar='PORT', default=0,
52 help='set the REQ channel port [default: random]')
53 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
54 help='set the heartbeat port [default: random]')
55 parser.add_argument('--no-stdout', action='store_true',
56 help='redirect stdout to the null device')
57 parser.add_argument('--no-stderr', action='store_true',
58 help='redirect stderr to the null device')
59
60 if sys.platform == 'win32':
61 parser.add_argument('--interrupt', type=int, metavar='HANDLE',
62 default=0, help='interrupt this process when '
63 'HANDLE is signaled')
64 parser.add_argument('--parent', type=int, metavar='HANDLE',
65 default=0, help='kill this process if the process '
66 'with HANDLE dies')
67 else:
68 parser.add_argument('--parent', action='store_true',
69 help='kill this process if its parent dies')
70
71 return parser
72
73
74 def make_kernel(namespace, kernel_factory,
75 out_stream_factory=None, display_hook_factory=None):
76 """ Creates a kernel, redirects stdout/stderr, and installs a display hook
77 and exception handler.
78 """
79 # Re-direct stdout/stderr, if necessary.
80 if namespace.no_stdout or namespace.no_stderr:
81 blackhole = file(os.devnull, 'w')
82 if namespace.no_stdout:
83 sys.stdout = sys.__stdout__ = blackhole
84 if namespace.no_stderr:
85 sys.stderr = sys.__stderr__ = blackhole
86
87 # Install minimal exception handling
88 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
89 ostream=sys.__stdout__)
13 from parentpoller import ParentPollerWindows
90 14
91 # Create a context, a session, and the kernel sockets.
92 io.raw_print("Starting the kernel at pid:", os.getpid())
93 context = zmq.Context()
94 # Uncomment this to try closing the context.
95 # atexit.register(context.close)
96 session = Session(username=u'kernel')
97 15
98 reply_socket = context.socket(zmq.XREP)
99 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
100 io.raw_print("XREP Channel on port", xrep_port)
101 16
102 pub_socket = context.socket(zmq.PUB)
103 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
104 io.raw_print("PUB Channel on port", pub_port)
105
106 req_socket = context.socket(zmq.XREQ)
107 req_port = bind_port(req_socket, namespace.ip, namespace.req)
108 io.raw_print("REQ Channel on port", req_port)
109
110 hb = Heartbeat(context, (namespace.ip, namespace.hb))
111 hb.start()
112 hb_port = hb.port
113 io.raw_print("Heartbeat REP Channel on port", hb_port)
114
115 # Helper to make it easier to connect to an existing kernel, until we have
116 # single-port connection negotiation fully implemented.
117 io.raw_print("To connect another client to this kernel, use:")
118 io.raw_print("-e --xreq {0} --sub {1} --rep {2} --hb {3}".format(
119 xrep_port, pub_port, req_port, hb_port))
120
121 # Redirect input streams and set a display hook.
122 if out_stream_factory:
123 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
124 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
125 if display_hook_factory:
126 sys.displayhook = display_hook_factory(session, pub_socket)
127
128 # Create the kernel.
129 kernel = kernel_factory(session=session, reply_socket=reply_socket,
130 pub_socket=pub_socket, req_socket=req_socket)
131 kernel.record_ports(xrep_port=xrep_port, pub_port=pub_port,
132 req_port=req_port, hb_port=hb_port)
133 return kernel
134
135
136 def start_kernel(namespace, kernel):
137 """ Starts a kernel.
138 """
139 # Configure this kernel process to poll the parent process, if necessary.
140 if sys.platform == 'win32':
141 if namespace.interrupt or namespace.parent:
142 poller = ParentPollerWindows(namespace.interrupt, namespace.parent)
143 poller.start()
144 elif namespace.parent:
145 poller = ParentPollerUnix()
146 poller.start()
147
148 # Start the kernel mainloop.
149 kernel.start()
150
151
152 def make_default_main(kernel_factory):
153 """ Creates the simplest possible kernel entry point.
154 """
155 def main():
156 namespace = make_argument_parser().parse_args()
157 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
158 start_kernel(namespace, kernel)
159 return main
160
161
162 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
163 stdin=None, stdout=None, stderr=None,
164 executable=None, independent=False, extra_arguments=[]):
17 def base_launch_kernel(code, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
18 ip=None, stdin=None, stdout=None, stderr=None,
19 executable=None, independent=False, extra_arguments=[]):
165 20 """ Launches a localhost kernel, binding to the specified ports.
166 21
167 22 Parameters
@@ -169,18 +24,21 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
169 24 code : str,
170 25 A string of Python code that imports and executes a kernel entry point.
171 26
172 xrep_port : int, optional
27 shell_port : int, optional
173 28 The port to use for XREP channel.
174 29
175 pub_port : int, optional
30 iopub_port : int, optional
176 31 The port to use for the SUB channel.
177 32
178 req_port : int, optional
33 stdin_port : int, optional
179 34 The port to use for the REQ (raw input) channel.
180 35
181 36 hb_port : int, optional
182 37 The port to use for the hearbeat REP channel.
183 38
39 ip : str, optional
40 The ip address the kernel will bind to.
41
184 42 stdin, stdout, stderr : optional (default None)
185 43 Standards streams, as defined in subprocess.Popen.
186 44
@@ -199,13 +57,13 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
199 57 Returns
200 58 -------
201 59 A tuple of form:
202 (kernel_process, xrep_port, pub_port, req_port)
60 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
203 61 where kernel_process is a Popen object and the ports are integers.
204 62 """
205 63 # Find open ports as necessary.
206 64 ports = []
207 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
208 int(req_port <= 0) + int(hb_port <= 0)
65 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
66 int(stdin_port <= 0) + int(hb_port <= 0)
209 67 for i in xrange(ports_needed):
210 68 sock = socket.socket()
211 69 sock.bind(('', 0))
@@ -214,28 +72,31 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
214 72 port = sock.getsockname()[1]
215 73 sock.close()
216 74 ports[i] = port
217 if xrep_port <= 0:
218 xrep_port = ports.pop(0)
219 if pub_port <= 0:
220 pub_port = ports.pop(0)
221 if req_port <= 0:
222 req_port = ports.pop(0)
75 if shell_port <= 0:
76 shell_port = ports.pop(0)
77 if iopub_port <= 0:
78 iopub_port = ports.pop(0)
79 if stdin_port <= 0:
80 stdin_port = ports.pop(0)
223 81 if hb_port <= 0:
224 82 hb_port = ports.pop(0)
225 83
226 84 # Build the kernel launch command.
227 85 if executable is None:
228 86 executable = sys.executable
229 arguments = [ executable, '-c', code, '--xrep', str(xrep_port),
230 '--pub', str(pub_port), '--req', str(req_port),
231 '--hb', str(hb_port) ]
87 arguments = [ executable, '-c', code, 'shell=%i'%shell_port,
88 'iopub=%i'%iopub_port, 'stdin=%i'%stdin_port,
89 'hb=%i'%hb_port
90 ]
91 if ip is not None:
92 arguments.append('ip=%s'%ip)
232 93 arguments.extend(extra_arguments)
233 94
234 95 # Spawn a kernel.
235 96 if sys.platform == 'win32':
236 97 # Create a Win32 event for interrupting the kernel.
237 98 interrupt_event = ParentPollerWindows.create_interrupt_event()
238 arguments += [ '--interrupt', str(int(interrupt_event)) ]
99 arguments += [ 'interrupt=%i'%interrupt_event ]
239 100
240 101 # If this process in running on pythonw, stdin, stdout, and stderr are
241 102 # invalid. Popen will fail unless they are suitably redirected. We don't
@@ -273,7 +134,7 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
273 134 handle = DuplicateHandle(pid, pid, pid, 0,
274 135 True, # Inheritable by new processes.
275 136 DUPLICATE_SAME_ACCESS)
276 proc = Popen(arguments + ['--parent', str(int(handle))],
137 proc = Popen(arguments + ['parent=%i'%int(handle)],
277 138 stdin=_stdin, stdout=_stdout, stderr=_stderr)
278 139
279 140 # Attach the interrupt event to the Popen objet so it can be used later.
@@ -293,7 +154,7 b' def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,'
293 154 proc = Popen(arguments, preexec_fn=lambda: os.setsid(),
294 155 stdin=stdin, stdout=stdout, stderr=stderr)
295 156 else:
296 proc = Popen(arguments + ['--parent'],
157 proc = Popen(arguments + ['parent=1'],
297 158 stdin=stdin, stdout=stdout, stderr=stderr)
298 159
299 return proc, xrep_port, pub_port, req_port, hb_port
160 return proc, shell_port, iopub_port, stdin_port, hb_port
@@ -27,37 +27,25 b' import zmq'
27 27
28 28 # Local imports.
29 29 from IPython.config.configurable import Configurable
30 from IPython.config.application import boolean_flag
31 from IPython.core.application import ProfileDir
32 from IPython.core.shellapp import (
33 InteractiveShellApp, shell_flags, shell_aliases
34 )
30 35 from IPython.utils import io
31 36 from IPython.utils.jsonutil import json_clean
32 37 from IPython.lib import pylabtools
33 from IPython.utils.traitlets import Instance, Float
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 start_kernel)
38 from IPython.utils.traitlets import (
39 List, Instance, Float, Dict, Bool, Int, Unicode, CaselessStrEnum
40 )
41
42 from entry_point import base_launch_kernel
43 from kernelapp import KernelApp, kernel_flags, kernel_aliases
36 44 from iostream import OutStream
37 45 from session import Session, Message
38 46 from zmqshell import ZMQInteractiveShell
39 47
40 #-----------------------------------------------------------------------------
41 # Globals
42 #-----------------------------------------------------------------------------
43
44 # Module-level logger
45 logger = logging.getLogger(__name__)
46 48
47 # FIXME: this needs to be done more cleanly later, once we have proper
48 # configuration support. This is a library, so it shouldn't set a stream
49 # handler, see:
50 # http://docs.python.org/library/logging.html#configuring-logging-for-a-library
51 # But this lets us at least do developer debugging for now by manually turning
52 # it on/off. And once we have full config support, the client entry points
53 # will select their logging handlers, as well as passing to this library the
54 # logging level.
55
56 if 0: # dbg - set to 1 to actually see the messages.
57 logger.addHandler(logging.StreamHandler())
58 logger.setLevel(logging.DEBUG)
59
60 # /FIXME
61 49
62 50 #-----------------------------------------------------------------------------
63 51 # Main kernel class
@@ -71,9 +59,10 b' class Kernel(Configurable):'
71 59
72 60 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
73 61 session = Instance(Session)
74 reply_socket = Instance('zmq.Socket')
75 pub_socket = Instance('zmq.Socket')
76 req_socket = Instance('zmq.Socket')
62 shell_socket = Instance('zmq.Socket')
63 iopub_socket = Instance('zmq.Socket')
64 stdin_socket = Instance('zmq.Socket')
65 log = Instance(logging.Logger)
77 66
78 67 # Private interface
79 68
@@ -100,7 +89,8 b' class Kernel(Configurable):'
100 89
101 90 # This is a dict of port number that the kernel is listening on. It is set
102 91 # by record_ports and used by connect_request.
103 _recorded_ports = None
92 _recorded_ports = Dict()
93
104 94
105 95
106 96 def __init__(self, **kwargs):
@@ -111,11 +101,11 b' class Kernel(Configurable):'
111 101 atexit.register(self._at_shutdown)
112 102
113 103 # Initialize the InteractiveShell subclass
114 self.shell = ZMQInteractiveShell.instance()
104 self.shell = ZMQInteractiveShell.instance(config=self.config)
115 105 self.shell.displayhook.session = self.session
116 self.shell.displayhook.pub_socket = self.pub_socket
106 self.shell.displayhook.pub_socket = self.iopub_socket
117 107 self.shell.display_pub.session = self.session
118 self.shell.display_pub.pub_socket = self.pub_socket
108 self.shell.display_pub.pub_socket = self.iopub_socket
119 109
120 110 # TMP - hack while developing
121 111 self.shell._reply_content = None
@@ -131,7 +121,7 b' class Kernel(Configurable):'
131 121 def do_one_iteration(self):
132 122 """Do one iteration of the kernel's evaluation loop.
133 123 """
134 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
124 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
135 125 if msg is None:
136 126 return
137 127
@@ -143,21 +133,20 b' class Kernel(Configurable):'
143 133 # Print some info about this message and leave a '--->' marker, so it's
144 134 # easier to trace visually the message chain when debugging. Each
145 135 # handler prints its message at the end.
146 # Eventually we'll move these from stdout to a logger.
147 logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
148 logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
136 self.log.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
137 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
149 138
150 139 # Find and call actual handler for message
151 140 handler = self.handlers.get(msg['msg_type'], None)
152 141 if handler is None:
153 logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
142 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
154 143 else:
155 144 handler(ident, msg)
156 145
157 146 # Check whether we should exit, in case the incoming message set the
158 147 # exit flag on
159 148 if self.shell.exit_now:
160 logger.debug('\nExiting IPython kernel...')
149 self.log.debug('\nExiting IPython kernel...')
161 150 # We do a normal, clean exit, which allows any actions registered
162 151 # via atexit (such as history saving) to take place.
163 152 sys.exit(0)
@@ -166,26 +155,27 b' class Kernel(Configurable):'
166 155 def start(self):
167 156 """ Start the kernel main loop.
168 157 """
158 poller = zmq.Poller()
159 poller.register(self.shell_socket, zmq.POLLIN)
169 160 while True:
170 161 try:
171 time.sleep(self._poll_interval)
162 # scale by extra factor of 10, because there is no
163 # reason for this to be anything less than ~ 0.1s
164 # since it is a real poller and will respond
165 # to events immediately
166 poller.poll(10*1000*self._poll_interval)
172 167 self.do_one_iteration()
173 168 except KeyboardInterrupt:
174 169 # Ctrl-C shouldn't crash the kernel
175 170 io.raw_print("KeyboardInterrupt caught in kernel")
176 171
177 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
172 def record_ports(self, ports):
178 173 """Record the ports that this kernel is using.
179 174
180 175 The creator of the Kernel instance must call this methods if they
181 176 want the :meth:`connect_request` method to return the port numbers.
182 177 """
183 self._recorded_ports = {
184 'xrep_port' : xrep_port,
185 'pub_port' : pub_port,
186 'req_port' : req_port,
187 'hb_port' : hb_port
188 }
178 self._recorded_ports = ports
189 179
190 180 #---------------------------------------------------------------------------
191 181 # Kernel request handlers
@@ -194,11 +184,11 b' class Kernel(Configurable):'
194 184 def _publish_pyin(self, code, parent):
195 185 """Publish the code request on the pyin stream."""
196 186
197 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
187 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
198 188
199 189 def execute_request(self, ident, parent):
200 190
201 status_msg = self.session.send(self.pub_socket,
191 status_msg = self.session.send(self.iopub_socket,
202 192 u'status',
203 193 {u'execution_state':u'busy'},
204 194 parent=parent
@@ -209,8 +199,8 b' class Kernel(Configurable):'
209 199 code = content[u'code']
210 200 silent = content[u'silent']
211 201 except:
212 logger.error("Got bad msg: ")
213 logger.error(str(Message(parent)))
202 self.log.error("Got bad msg: ")
203 self.log.error(str(Message(parent)))
214 204 return
215 205
216 206 shell = self.shell # we'll need this a lot here
@@ -298,14 +288,14 b' class Kernel(Configurable):'
298 288 time.sleep(self._execute_sleep)
299 289
300 290 # Send the reply.
301 reply_msg = self.session.send(self.reply_socket, u'execute_reply',
291 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
302 292 reply_content, parent, ident=ident)
303 logger.debug(str(reply_msg))
293 self.log.debug(str(reply_msg))
304 294
305 295 if reply_msg['content']['status'] == u'error':
306 296 self._abort_queue()
307 297
308 status_msg = self.session.send(self.pub_socket,
298 status_msg = self.session.send(self.iopub_socket,
309 299 u'status',
310 300 {u'execution_state':u'idle'},
311 301 parent=parent
@@ -316,17 +306,17 b' class Kernel(Configurable):'
316 306 matches = {'matches' : matches,
317 307 'matched_text' : txt,
318 308 'status' : 'ok'}
319 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
309 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
320 310 matches, parent, ident)
321 logger.debug(str(completion_msg))
311 self.log.debug(str(completion_msg))
322 312
323 313 def object_info_request(self, ident, parent):
324 314 object_info = self.shell.object_inspect(parent['content']['oname'])
325 315 # Before we send this object over, we scrub it for JSON usage
326 316 oinfo = json_clean(object_info)
327 msg = self.session.send(self.reply_socket, 'object_info_reply',
317 msg = self.session.send(self.shell_socket, 'object_info_reply',
328 318 oinfo, parent, ident)
329 logger.debug(msg)
319 self.log.debug(msg)
330 320
331 321 def history_request(self, ident, parent):
332 322 # We need to pull these out, as passing **kwargs doesn't work with
@@ -353,18 +343,18 b' class Kernel(Configurable):'
353 343 else:
354 344 hist = []
355 345 content = {'history' : list(hist)}
356 msg = self.session.send(self.reply_socket, 'history_reply',
346 msg = self.session.send(self.shell_socket, 'history_reply',
357 347 content, parent, ident)
358 logger.debug(str(msg))
348 self.log.debug(str(msg))
359 349
360 350 def connect_request(self, ident, parent):
361 351 if self._recorded_ports is not None:
362 352 content = self._recorded_ports.copy()
363 353 else:
364 354 content = {}
365 msg = self.session.send(self.reply_socket, 'connect_reply',
355 msg = self.session.send(self.shell_socket, 'connect_reply',
366 356 content, parent, ident)
367 logger.debug(msg)
357 self.log.debug(msg)
368 358
369 359 def shutdown_request(self, ident, parent):
370 360 self.shell.exit_now = True
@@ -377,19 +367,19 b' class Kernel(Configurable):'
377 367
378 368 def _abort_queue(self):
379 369 while True:
380 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
370 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
381 371 if msg is None:
382 372 break
383 373 else:
384 374 assert ident is not None, \
385 375 "Unexpected missing message part."
386 376
387 logger.debug("Aborting:\n"+str(Message(msg)))
377 self.log.debug("Aborting:\n"+str(Message(msg)))
388 378 msg_type = msg['msg_type']
389 379 reply_type = msg_type.split('_')[0] + '_reply'
390 reply_msg = self.session.send(self.reply_socket, reply_type,
380 reply_msg = self.session.send(self.shell_socket, reply_type,
391 381 {'status' : 'aborted'}, msg, ident=ident)
392 logger.debug(reply_msg)
382 self.log.debug(reply_msg)
393 383 # We need to wait a bit for requests to come in. This can probably
394 384 # be set shorter for true asynchronous clients.
395 385 time.sleep(0.1)
@@ -401,15 +391,15 b' class Kernel(Configurable):'
401 391
402 392 # Send the input request.
403 393 content = dict(prompt=prompt)
404 msg = self.session.send(self.req_socket, u'input_request', content, parent)
394 msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
405 395
406 396 # Await a response.
407 ident, reply = self.session.recv(self.req_socket, 0)
397 ident, reply = self.session.recv(self.stdin_socket, 0)
408 398 try:
409 399 value = reply['content']['value']
410 400 except:
411 logger.error("Got bad raw_input reply: ")
412 logger.error(str(Message(parent)))
401 self.log.error("Got bad raw_input reply: ")
402 self.log.error(str(Message(parent)))
413 403 value = ''
414 404 return value
415 405
@@ -461,9 +451,9 b' class Kernel(Configurable):'
461 451 """
462 452 # io.rprint("Kernel at_shutdown") # dbg
463 453 if self._shutdown_message is not None:
464 self.session.send(self.reply_socket, self._shutdown_message)
465 self.session.send(self.pub_socket, self._shutdown_message)
466 logger.debug(str(self._shutdown_message))
454 self.session.send(self.shell_socket, self._shutdown_message)
455 self.session.send(self.iopub_socket, self._shutdown_message)
456 self.log.debug(str(self._shutdown_message))
467 457 # A very short sleep to give zmq time to flush its message buffers
468 458 # before Python truly shuts down.
469 459 time.sleep(0.01)
@@ -569,120 +559,114 b' class GTKKernel(Kernel):'
569 559
570 560
571 561 #-----------------------------------------------------------------------------
572 # Kernel main and launch functions
562 # Aliases and Flags for the IPKernelApp
573 563 #-----------------------------------------------------------------------------
574 564
575 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
576 stdin=None, stdout=None, stderr=None,
577 executable=None, independent=False, pylab=False, colors=None):
578 """Launches a localhost kernel, binding to the specified ports.
565 flags = dict(kernel_flags)
566 flags.update(shell_flags)
579 567
580 Parameters
581 ----------
582 ip : str, optional
583 The ip address the kernel will bind to.
584
585 xrep_port : int, optional
586 The port to use for XREP channel.
568 addflag = lambda *args: flags.update(boolean_flag(*args))
587 569
588 pub_port : int, optional
589 The port to use for the SUB channel.
570 flags['pylab'] = (
571 {'IPKernelApp' : {'pylab' : 'auto'}},
572 """Pre-load matplotlib and numpy for interactive use with
573 the default matplotlib backend."""
574 )
590 575
591 req_port : int, optional
592 The port to use for the REQ (raw input) channel.
576 aliases = dict(kernel_aliases)
577 aliases.update(shell_aliases)
593 578
594 hb_port : int, optional
595 The port to use for the hearbeat REP channel.
579 # it's possible we don't want short aliases for *all* of these:
580 aliases.update(dict(
581 pylab='IPKernelApp.pylab',
582 ))
596 583
597 stdin, stdout, stderr : optional (default None)
598 Standards streams, as defined in subprocess.Popen.
584 #-----------------------------------------------------------------------------
585 # The IPKernelApp class
586 #-----------------------------------------------------------------------------
599 587
600 executable : str, optional (default sys.executable)
601 The Python executable to use for the kernel process.
588 class IPKernelApp(KernelApp, InteractiveShellApp):
589 name = 'ipkernel'
590
591 aliases = Dict(aliases)
592 flags = Dict(flags)
593 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
594 # configurables
595 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
596 config=True,
597 help="""Pre-load matplotlib and numpy for interactive use,
598 selecting a particular matplotlib backend and loop integration.
599 """
600 )
601 def initialize(self, argv=None):
602 super(IPKernelApp, self).initialize(argv)
603 self.init_shell()
604 self.init_extensions()
605 self.init_code()
606
607 def init_kernel(self):
608 kernel_factory = Kernel
609
610 kernel_map = {
611 'qt' : QtKernel,
612 'qt4': QtKernel,
613 'inline': Kernel,
614 'osx': TkKernel,
615 'wx' : WxKernel,
616 'tk' : TkKernel,
617 'gtk': GTKKernel,
618 }
619
620 if self.pylab:
621 key = None if self.pylab == 'auto' else self.pylab
622 gui, backend = pylabtools.find_gui_and_backend(key)
623 kernel_factory = kernel_map.get(gui)
624 if kernel_factory is None:
625 raise ValueError('GUI is not supported: %r' % gui)
626 pylabtools.activate_matplotlib(backend)
627
628 kernel = kernel_factory(config=self.config, session=self.session,
629 shell_socket=self.shell_socket,
630 iopub_socket=self.iopub_socket,
631 stdin_socket=self.stdin_socket,
632 log=self.log
633 )
634 self.kernel = kernel
635 kernel.record_ports(self.ports)
636
637 if self.pylab:
638 pylabtools.import_pylab(kernel.shell.user_ns, backend,
639 shell=kernel.shell)
640
641 def init_shell(self):
642 self.shell = self.kernel.shell
602 643
603 independent : bool, optional (default False)
604 If set, the kernel process is guaranteed to survive if this process
605 dies. If not set, an effort is made to ensure that the kernel is killed
606 when this process dies. Note that in this case it is still good practice
607 to kill kernels manually before exiting.
608 644
609 pylab : bool or string, optional (default False)
610 If not False, the kernel will be launched with pylab enabled. If a
611 string is passed, matplotlib will use the specified backend. Otherwise,
612 matplotlib's default backend will be used.
645 #-----------------------------------------------------------------------------
646 # Kernel main and launch functions
647 #-----------------------------------------------------------------------------
613 648
614 colors : None or string, optional (default None)
615 If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
649 def launch_kernel(*args, **kwargs):
650 """Launches a localhost IPython kernel, binding to the specified ports.
651
652 This function simply calls entry_point.base_launch_kernel with the right first
653 command to start an ipkernel. See base_launch_kernel for arguments.
616 654
617 655 Returns
618 656 -------
619 657 A tuple of form:
620 (kernel_process, xrep_port, pub_port, req_port)
658 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
621 659 where kernel_process is a Popen object and the ports are integers.
622 660 """
623 extra_arguments = []
624 if pylab:
625 extra_arguments.append('--pylab')
626 if isinstance(pylab, basestring):
627 extra_arguments.append(pylab)
628 if ip is not None:
629 extra_arguments.append('--ip')
630 if isinstance(ip, basestring):
631 extra_arguments.append(ip)
632 if colors is not None:
633 extra_arguments.append('--colors')
634 extra_arguments.append(colors)
635 661 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
636 xrep_port, pub_port, req_port, hb_port,
637 stdin, stdout, stderr,
638 executable, independent, extra_arguments)
662 *args, **kwargs)
639 663
640 664
641 665 def main():
642 """ The IPython kernel main entry point.
643 """
644 parser = make_argument_parser()
645 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
646 const='auto', help = \
647 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
648 given, the GUI backend is matplotlib's, otherwise use one of: \
649 ['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
650 parser.add_argument('--colors',
651 type=str, dest='colors',
652 help="Set the color scheme (NoColor, Linux, and LightBG).",
653 metavar='ZMQInteractiveShell.colors')
654 namespace = parser.parse_args()
655
656 kernel_class = Kernel
657
658 kernel_classes = {
659 'qt' : QtKernel,
660 'qt4': QtKernel,
661 'inline': Kernel,
662 'osx': TkKernel,
663 'wx' : WxKernel,
664 'tk' : TkKernel,
665 'gtk': GTKKernel,
666 }
667 if namespace.pylab:
668 if namespace.pylab == 'auto':
669 gui, backend = pylabtools.find_gui_and_backend()
670 else:
671 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
672 kernel_class = kernel_classes.get(gui)
673 if kernel_class is None:
674 raise ValueError('GUI is not supported: %r' % gui)
675 pylabtools.activate_matplotlib(backend)
676 if namespace.colors:
677 ZMQInteractiveShell.colors=namespace.colors
678
679 kernel = make_kernel(namespace, kernel_class, OutStream)
680
681 if namespace.pylab:
682 pylabtools.import_pylab(kernel.shell.user_ns, backend,
683 shell=kernel.shell)
684
685 start_kernel(namespace, kernel)
666 """Run an IPKernel as an application"""
667 app = IPKernelApp.instance()
668 app.initialize()
669 app.start()
686 670
687 671
688 672 if __name__ == '__main__':
@@ -32,6 +32,7 b' from zmq import POLLIN, POLLOUT, POLLERR'
32 32 from zmq.eventloop import ioloop
33 33
34 34 # Local imports.
35 from IPython.config.loader import Config
35 36 from IPython.utils import io
36 37 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 38 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
@@ -77,7 +78,7 b' def validate_string_dict(dct):'
77 78 # ZMQ Socket Channel classes
78 79 #-----------------------------------------------------------------------------
79 80
80 class ZmqSocketChannel(Thread):
81 class ZMQSocketChannel(Thread):
81 82 """The base class for the channels that use ZMQ sockets.
82 83 """
83 84 context = None
@@ -99,7 +100,7 b' class ZmqSocketChannel(Thread):'
99 100 address : tuple
100 101 Standard (ip, port) tuple that the kernel is listening on.
101 102 """
102 super(ZmqSocketChannel, self).__init__()
103 super(ZMQSocketChannel, self).__init__()
103 104 self.daemon = True
104 105
105 106 self.context = context
@@ -173,14 +174,14 b' class ZmqSocketChannel(Thread):'
173 174 self.ioloop.add_callback(drop_io_state_callback)
174 175
175 176
176 class XReqSocketChannel(ZmqSocketChannel):
177 class ShellSocketChannel(ZMQSocketChannel):
177 178 """The XREQ channel for issues request/replies to the kernel.
178 179 """
179 180
180 181 command_queue = None
181 182
182 183 def __init__(self, context, session, address):
183 super(XReqSocketChannel, self).__init__(context, session, address)
184 super(ShellSocketChannel, self).__init__(context, session, address)
184 185 self.command_queue = Queue()
185 186 self.ioloop = ioloop.IOLoop()
186 187
@@ -196,7 +197,7 b' class XReqSocketChannel(ZmqSocketChannel):'
196 197
197 198 def stop(self):
198 199 self.ioloop.stop()
199 super(XReqSocketChannel, self).stop()
200 super(ShellSocketChannel, self).stop()
200 201
201 202 def call_handlers(self, msg):
202 203 """This method is called in the ioloop thread when a message arrives.
@@ -382,7 +383,7 b' class XReqSocketChannel(ZmqSocketChannel):'
382 383 self.add_io_state(POLLOUT)
383 384
384 385
385 class SubSocketChannel(ZmqSocketChannel):
386 class SubSocketChannel(ZMQSocketChannel):
386 387 """The SUB channel which listens for messages that the kernel publishes.
387 388 """
388 389
@@ -469,13 +470,13 b' class SubSocketChannel(ZmqSocketChannel):'
469 470 self._flushed = True
470 471
471 472
472 class RepSocketChannel(ZmqSocketChannel):
473 class StdInSocketChannel(ZMQSocketChannel):
473 474 """A reply channel to handle raw_input requests that the kernel makes."""
474 475
475 476 msg_queue = None
476 477
477 478 def __init__(self, context, session, address):
478 super(RepSocketChannel, self).__init__(context, session, address)
479 super(StdInSocketChannel, self).__init__(context, session, address)
479 480 self.ioloop = ioloop.IOLoop()
480 481 self.msg_queue = Queue()
481 482
@@ -491,7 +492,7 b' class RepSocketChannel(ZmqSocketChannel):'
491 492
492 493 def stop(self):
493 494 self.ioloop.stop()
494 super(RepSocketChannel, self).stop()
495 super(StdInSocketChannel, self).stop()
495 496
496 497 def call_handlers(self, msg):
497 498 """This method is called in the ioloop thread when a message arrives.
@@ -540,7 +541,7 b' class RepSocketChannel(ZmqSocketChannel):'
540 541 self.add_io_state(POLLOUT)
541 542
542 543
543 class HBSocketChannel(ZmqSocketChannel):
544 class HBSocketChannel(ZMQSocketChannel):
544 545 """The heartbeat channel which monitors the kernel heartbeat.
545 546
546 547 Note that the heartbeat channel is paused by default. As long as you start
@@ -676,44 +677,51 b' class KernelManager(HasTraits):'
676 677 The REP channel is for the kernel to request stdin (raw_input) from the
677 678 frontend.
678 679 """
680 # config object for passing to child configurables
681 config = Instance(Config)
682
679 683 # The PyZMQ Context to use for communication with the kernel.
680 context = Instance(zmq.Context,(),{})
684 context = Instance(zmq.Context)
685 def _context_default(self):
686 return zmq.Context.instance()
681 687
682 688 # The Session to use for communication with the kernel.
683 session = Instance(Session,(),{})
689 session = Instance(Session)
684 690
685 691 # The kernel process with which the KernelManager is communicating.
686 692 kernel = Instance(Popen)
687 693
688 694 # The addresses for the communication channels.
689 xreq_address = TCPAddress((LOCALHOST, 0))
695 shell_address = TCPAddress((LOCALHOST, 0))
690 696 sub_address = TCPAddress((LOCALHOST, 0))
691 rep_address = TCPAddress((LOCALHOST, 0))
697 stdin_address = TCPAddress((LOCALHOST, 0))
692 698 hb_address = TCPAddress((LOCALHOST, 0))
693 699
694 700 # The classes to use for the various channels.
695 xreq_channel_class = Type(XReqSocketChannel)
701 shell_channel_class = Type(ShellSocketChannel)
696 702 sub_channel_class = Type(SubSocketChannel)
697 rep_channel_class = Type(RepSocketChannel)
703 stdin_channel_class = Type(StdInSocketChannel)
698 704 hb_channel_class = Type(HBSocketChannel)
699 705
700 706 # Protected traits.
701 707 _launch_args = Any
702 _xreq_channel = Any
708 _shell_channel = Any
703 709 _sub_channel = Any
704 _rep_channel = Any
710 _stdin_channel = Any
705 711 _hb_channel = Any
706 712
707 713 def __init__(self, **kwargs):
708 714 super(KernelManager, self).__init__(**kwargs)
715 if self.session is None:
716 self.session = Session(config=self.config)
709 717 # Uncomment this to try closing the context.
710 # atexit.register(self.context.close)
718 # atexit.register(self.context.term)
711 719
712 720 #--------------------------------------------------------------------------
713 721 # Channel management methods:
714 722 #--------------------------------------------------------------------------
715 723
716 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
724 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
717 725 """Starts the channels for this kernel.
718 726
719 727 This will create the channels if they do not exist and then start
@@ -721,32 +729,32 b' class KernelManager(HasTraits):'
721 729 must first call :method:`start_kernel`. If the channels have been
722 730 stopped and you call this, :class:`RuntimeError` will be raised.
723 731 """
724 if xreq:
725 self.xreq_channel.start()
732 if shell:
733 self.shell_channel.start()
726 734 if sub:
727 735 self.sub_channel.start()
728 if rep:
729 self.rep_channel.start()
736 if stdin:
737 self.stdin_channel.start()
730 738 if hb:
731 739 self.hb_channel.start()
732 740
733 741 def stop_channels(self):
734 742 """Stops all the running channels for this kernel.
735 743 """
736 if self.xreq_channel.is_alive():
737 self.xreq_channel.stop()
744 if self.shell_channel.is_alive():
745 self.shell_channel.stop()
738 746 if self.sub_channel.is_alive():
739 747 self.sub_channel.stop()
740 if self.rep_channel.is_alive():
741 self.rep_channel.stop()
748 if self.stdin_channel.is_alive():
749 self.stdin_channel.stop()
742 750 if self.hb_channel.is_alive():
743 751 self.hb_channel.stop()
744 752
745 753 @property
746 754 def channels_running(self):
747 755 """Are any of the channels created and running?"""
748 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
749 self.rep_channel.is_alive() or self.hb_channel.is_alive())
756 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
757 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
750 758
751 759 #--------------------------------------------------------------------------
752 760 # Kernel process management methods:
@@ -766,10 +774,10 b' class KernelManager(HasTraits):'
766 774 **kw : optional
767 775 See respective options for IPython and Python kernels.
768 776 """
769 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
770 self.rep_address, self.hb_address
771 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
772 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
777 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
778 self.stdin_address, self.hb_address
779 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
780 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
773 781 raise RuntimeError("Can only launch a kernel on a local interface. "
774 782 "Make sure that the '*_address' attributes are "
775 783 "configured properly. "
@@ -782,11 +790,11 b' class KernelManager(HasTraits):'
782 790 else:
783 791 from pykernel import launch_kernel
784 792 self.kernel, xrep, pub, req, _hb = launch_kernel(
785 xrep_port=xreq[1], pub_port=sub[1],
786 req_port=rep[1], hb_port=hb[1], **kw)
787 self.xreq_address = (xreq[0], xrep)
793 shell_port=shell[1], iopub_port=sub[1],
794 stdin_port=stdin[1], hb_port=hb[1], **kw)
795 self.shell_address = (shell[0], xrep)
788 796 self.sub_address = (sub[0], pub)
789 self.rep_address = (rep[0], req)
797 self.stdin_address = (stdin[0], req)
790 798 self.hb_address = (hb[0], _hb)
791 799
792 800 def shutdown_kernel(self, restart=False):
@@ -805,7 +813,7 b' class KernelManager(HasTraits):'
805 813 # Don't send any additional kernel kill messages immediately, to give
806 814 # the kernel a chance to properly execute shutdown actions. Wait for at
807 815 # most 1s, checking every 0.1s.
808 self.xreq_channel.shutdown(restart=restart)
816 self.shell_channel.shutdown(restart=restart)
809 817 for i in range(10):
810 818 if self.is_alive:
811 819 time.sleep(0.1)
@@ -931,13 +939,13 b' class KernelManager(HasTraits):'
931 939 #--------------------------------------------------------------------------
932 940
933 941 @property
934 def xreq_channel(self):
942 def shell_channel(self):
935 943 """Get the REQ socket channel object to make requests of the kernel."""
936 if self._xreq_channel is None:
937 self._xreq_channel = self.xreq_channel_class(self.context,
944 if self._shell_channel is None:
945 self._shell_channel = self.shell_channel_class(self.context,
938 946 self.session,
939 self.xreq_address)
940 return self._xreq_channel
947 self.shell_address)
948 return self._shell_channel
941 949
942 950 @property
943 951 def sub_channel(self):
@@ -949,13 +957,13 b' class KernelManager(HasTraits):'
949 957 return self._sub_channel
950 958
951 959 @property
952 def rep_channel(self):
960 def stdin_channel(self):
953 961 """Get the REP socket channel object to handle stdin (raw_input)."""
954 if self._rep_channel is None:
955 self._rep_channel = self.rep_channel_class(self.context,
962 if self._stdin_channel is None:
963 self._stdin_channel = self.stdin_channel_class(self.context,
956 964 self.session,
957 self.rep_address)
958 return self._rep_channel
965 self.stdin_address)
966 return self._stdin_channel
959 967
960 968 @property
961 969 def hb_channel(self):
@@ -25,10 +25,11 b' import traceback'
25 25 import zmq
26 26
27 27 # Local imports.
28 from IPython.utils.traitlets import HasTraits, Instance, Float
28 from IPython.utils.traitlets import HasTraits, Instance, Dict, Float
29 29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_default_main
30 from entry_point import base_launch_kernel
31 31 from session import Session, Message
32 from kernelapp import KernelApp
32 33
33 34 #-----------------------------------------------------------------------------
34 35 # Main kernel class
@@ -49,16 +50,17 b' class Kernel(HasTraits):'
49 50
50 51 # This is a dict of port number that the kernel is listening on. It is set
51 52 # by record_ports and used by connect_request.
52 _recorded_ports = None
53 _recorded_ports = Dict()
53 54
54 55 #---------------------------------------------------------------------------
55 56 # Kernel interface
56 57 #---------------------------------------------------------------------------
57 58
58 59 session = Instance(Session)
59 reply_socket = Instance('zmq.Socket')
60 pub_socket = Instance('zmq.Socket')
61 req_socket = Instance('zmq.Socket')
60 shell_socket = Instance('zmq.Socket')
61 iopub_socket = Instance('zmq.Socket')
62 stdin_socket = Instance('zmq.Socket')
63 log = Instance('logging.Logger')
62 64
63 65 def __init__(self, **kwargs):
64 66 super(Kernel, self).__init__(**kwargs)
@@ -78,29 +80,23 b' class Kernel(HasTraits):'
78 80 """ Start the kernel main loop.
79 81 """
80 82 while True:
81 ident,msg = self.session.recv(self.reply_socket,0)
83 ident,msg = self.session.recv(self.shell_socket,0)
82 84 assert ident is not None, "Missing message part."
83 85 omsg = Message(msg)
84 print>>sys.__stdout__
85 print>>sys.__stdout__, omsg
86 self.log.debug(str(omsg))
86 87 handler = self.handlers.get(omsg.msg_type, None)
87 88 if handler is None:
88 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
89 self.log.error("UNKNOWN MESSAGE TYPE: %s"%omsg)
89 90 else:
90 91 handler(ident, omsg)
91 92
92 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
93 def record_ports(self, ports):
93 94 """Record the ports that this kernel is using.
94 95
95 96 The creator of the Kernel instance must call this methods if they
96 97 want the :meth:`connect_request` method to return the port numbers.
97 98 """
98 self._recorded_ports = {
99 'xrep_port' : xrep_port,
100 'pub_port' : pub_port,
101 'req_port' : req_port,
102 'hb_port' : hb_port
103 }
99 self._recorded_ports = ports
104 100
105 101 #---------------------------------------------------------------------------
106 102 # Kernel request handlers
@@ -110,10 +106,9 b' class Kernel(HasTraits):'
110 106 try:
111 107 code = parent[u'content'][u'code']
112 108 except:
113 print>>sys.__stderr__, "Got bad msg: "
114 print>>sys.__stderr__, Message(parent)
109 self.log.error("Got bad msg: %s"%Message(parent))
115 110 return
116 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
111 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
117 112
118 113 try:
119 114 comp_code = self.compiler(code, '<zmq-kernel>')
@@ -138,7 +133,7 b' class Kernel(HasTraits):'
138 133 u'ename' : unicode(etype.__name__),
139 134 u'evalue' : unicode(evalue)
140 135 }
141 exc_msg = self.session.send(self.pub_socket, u'pyerr', exc_content, parent)
136 exc_msg = self.session.send(self.iopub_socket, u'pyerr', exc_content, parent)
142 137 reply_content = exc_content
143 138 else:
144 139 reply_content = { 'status' : 'ok', 'payload' : {} }
@@ -153,32 +148,32 b' class Kernel(HasTraits):'
153 148 time.sleep(self._execute_sleep)
154 149
155 150 # Send the reply.
156 reply_msg = self.session.send(self.reply_socket, u'execute_reply', reply_content, parent, ident=ident)
157 print>>sys.__stdout__, Message(reply_msg)
151 reply_msg = self.session.send(self.shell_socket, u'execute_reply', reply_content, parent, ident=ident)
152 self.log.debug(Message(reply_msg))
158 153 if reply_msg['content']['status'] == u'error':
159 154 self._abort_queue()
160 155
161 156 def complete_request(self, ident, parent):
162 157 matches = {'matches' : self._complete(parent),
163 158 'status' : 'ok'}
164 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
159 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
165 160 matches, parent, ident)
166 print >> sys.__stdout__, completion_msg
161 self.log.debug(completion_msg)
167 162
168 163 def object_info_request(self, ident, parent):
169 164 context = parent['content']['oname'].split('.')
170 165 object_info = self._object_info(context)
171 msg = self.session.send(self.reply_socket, 'object_info_reply',
166 msg = self.session.send(self.shell_socket, 'object_info_reply',
172 167 object_info, parent, ident)
173 print >> sys.__stdout__, msg
168 self.log.debug(msg)
174 169
175 170 def shutdown_request(self, ident, parent):
176 171 content = dict(parent['content'])
177 msg = self.session.send(self.reply_socket, 'shutdown_reply',
172 msg = self.session.send(self.shell_socket, 'shutdown_reply',
178 173 content, parent, ident)
179 msg = self.session.send(self.pub_socket, 'shutdown_reply',
174 msg = self.session.send(self.iopub_socket, 'shutdown_reply',
180 175 content, parent, ident)
181 print >> sys.__stdout__, msg
176 self.log.debug(msg)
182 177 time.sleep(0.1)
183 178 sys.exit(0)
184 179
@@ -188,17 +183,17 b' class Kernel(HasTraits):'
188 183
189 184 def _abort_queue(self):
190 185 while True:
191 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
186 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
192 187 if msg is None:
188 # msg=None on EAGAIN
193 189 break
194 190 else:
195 assert ident is not None, "Unexpected missing message part."
196 print>>sys.__stdout__, "Aborting:"
197 print>>sys.__stdout__, Message(msg)
191 assert ident is not None, "Missing message part."
192 self.log.debug("Aborting: %s"%Message(msg))
198 193 msg_type = msg['msg_type']
199 194 reply_type = msg_type.split('_')[0] + '_reply'
200 reply_msg = self.session.send(self.reply_socket, reply_type, {'status':'aborted'}, msg, ident=ident)
201 print>>sys.__stdout__, Message(reply_msg)
195 reply_msg = self.session.send(self.shell_socket, reply_type, {'status':'aborted'}, msg, ident=ident)
196 self.log.debug(Message(reply_msg))
202 197 # We need to wait a bit for requests to come in. This can probably
203 198 # be set shorter for true asynchronous clients.
204 199 time.sleep(0.1)
@@ -210,15 +205,14 b' class Kernel(HasTraits):'
210 205
211 206 # Send the input request.
212 207 content = dict(prompt=prompt)
213 msg = self.session.send(self.req_socket, u'input_request', content, parent)
208 msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
214 209
215 210 # Await a response.
216 ident,reply = self.session.recv(self.req_socket, 0)
211 ident,reply = self.session.recv(self.stdin_socket, 0)
217 212 try:
218 213 value = reply['content']['value']
219 214 except:
220 print>>sys.__stderr__, "Got bad raw_input reply: "
221 print>>sys.__stderr__, Message(parent)
215 self.log.error("Got bad raw_input reply: %s"%Message(parent))
222 216 value = ''
223 217 return value
224 218
@@ -259,58 +253,26 b' class Kernel(HasTraits):'
259 253 # Kernel main and launch functions
260 254 #-----------------------------------------------------------------------------
261 255
262 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
263 stdin=None, stdout=None, stderr=None,
264 executable=None, independent=False):
265 """ Launches a localhost kernel, binding to the specified ports.
266
267 Parameters
268 ----------
269 ip : str, optional
270 The ip address the kernel will bind to.
256 def launch_kernel(*args, **kwargs):
257 """ Launches a simple Python kernel, binding to the specified ports.
271 258
272 xrep_port : int, optional
273 The port to use for XREP channel.
274
275 pub_port : int, optional
276 The port to use for the SUB channel.
277
278 req_port : int, optional
279 The port to use for the REQ (raw input) channel.
280
281 hb_port : int, optional
282 The port to use for the hearbeat REP channel.
283
284 stdin, stdout, stderr : optional (default None)
285 Standards streams, as defined in subprocess.Popen.
286
287 executable : str, optional (default sys.executable)
288 The Python executable to use for the kernel process.
289
290 independent : bool, optional (default False)
291 If set, the kernel process is guaranteed to survive if this process
292 dies. If not set, an effort is made to ensure that the kernel is killed
293 when this process dies. Note that in this case it is still good practice
294 to kill kernels manually before exiting.
259 This function simply calls entry_point.base_launch_kernel with the right first
260 command to start a pykernel. See base_launch_kernel for arguments.
295 261
296 262 Returns
297 263 -------
298 264 A tuple of form:
299 (kernel_process, xrep_port, pub_port, req_port)
265 (kernel_process, xrep_port, pub_port, req_port, hb_port)
300 266 where kernel_process is a Popen object and the ports are integers.
301 267 """
302 extra_arguments = []
303 if ip is not None:
304 extra_arguments.append('--ip')
305 if isinstance(ip, basestring):
306 extra_arguments.append(ip)
307
308 268 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
309 xrep_port, pub_port, req_port, hb_port,
310 stdin, stdout, stderr,
311 executable, independent, extra_arguments)
269 *args, **kwargs)
312 270
313 main = make_default_main(Kernel)
271 def main():
272 """Run a PyKernel as an application"""
273 app = KernelApp.instance()
274 app.initialize()
275 app.start()
314 276
315 277 if __name__ == '__main__':
316 278 main()
@@ -10,12 +10,46 b' import sys'
10 10
11 11 # Third-party imports
12 12 import matplotlib
13 from matplotlib.backends.backend_svg import new_figure_manager
13 from matplotlib.backends.backend_agg import new_figure_manager
14 14 from matplotlib._pylab_helpers import Gcf
15 15
16 16 # Local imports.
17 from IPython.config.configurable import SingletonConfigurable
17 18 from IPython.core.displaypub import publish_display_data
18 from IPython.lib.pylabtools import figure_to_svg
19 from IPython.lib.pylabtools import print_figure, select_figure_format
20 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum
21 #-----------------------------------------------------------------------------
22 # Configurable for inline backend options
23 #-----------------------------------------------------------------------------
24
25 class InlineBackendConfig(SingletonConfigurable):
26 """An object to store configuration of the inline backend."""
27
28 # The typical default figure size is too large for inline use,
29 # so we shrink the figure size to 6x4, and tweak fonts to
30 # make that fit. This is configurable via Global.pylab_inline_rc,
31 # or rather it will be once the zmq kernel is hooked up to
32 # the config system.
33 rc = Dict({'figure.figsize': (6.0,4.0),
34 # 12pt labels get cutoff on 6x4 logplots, so use 10pt.
35 'font.size': 10,
36 # 10pt still needs a little more room on the xlabel:
37 'figure.subplot.bottom' : .125
38 }, config=True,
39 help="""Subset of matplotlib rcParams that should be different for the
40 inline backend."""
41 )
42 figure_format = CaselessStrEnum(['svg', 'png'], default_value='png', config=True,
43 help="The image format for figures with the inline backend.")
44
45 def _figure_format_changed(self, name, old, new):
46 if self.shell is None:
47 return
48 else:
49 select_figure_format(self.shell, new)
50
51 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
52
19 53
20 54 #-----------------------------------------------------------------------------
21 55 # Functions
@@ -32,7 +66,7 b' def show(close=True):'
32 66 removed from the internal list of figures.
33 67 """
34 68 for figure_manager in Gcf.get_all_fig_managers():
35 send_svg_figure(figure_manager.canvas.figure)
69 send_figure(figure_manager.canvas.figure)
36 70 if close:
37 71 matplotlib.pyplot.close('all')
38 72
@@ -50,8 +84,8 b' def draw_if_interactive():'
50 84 show._draw_called = True
51 85
52 86
53 def flush_svg():
54 """Call show, close all open figures, sending all SVG images.
87 def flush_figures():
88 """Call show, close all open figures, sending all figure images.
55 89
56 90 This is meant to be called automatically and will call show() if, during
57 91 prior code execution, there had been any calls to draw_if_interactive.
@@ -61,21 +95,23 b' def flush_svg():'
61 95 show._draw_called = False
62 96
63 97
64 def send_svg_figure(fig):
65 """Draw the current figure and send it as an SVG payload.
98 def send_figure(fig):
99 """Draw the current figure and send it as a PNG payload.
66 100 """
67 101 # For an empty figure, don't even bother calling figure_to_svg, to avoid
68 102 # big blank spaces in the qt console
69 103 if not fig.axes:
70 104 return
71
72 svg = figure_to_svg(fig)
105 fmt = InlineBackendConfig.instance().figure_format
106 data = print_figure(fig, fmt)
107 mimetypes = { 'png' : 'image/png', 'svg' : 'image/svg+xml' }
108 mime = mimetypes[fmt]
73 109 # flush text streams before sending figures, helps a little with output
74 110 # synchronization in the console (though it's a bandaid, not a real sln)
75 111 sys.stdout.flush(); sys.stderr.flush()
76 112 publish_display_data(
77 'IPython.zmq.pylab.backend_inline.send_svg_figure',
113 'IPython.zmq.pylab.backend_inline.send_figure',
78 114 'Matplotlib Plot',
79 {'image/svg+xml' : svg}
115 {mime : data}
80 116 )
81 117
This diff has been collapsed as it changes many lines, (597 lines changed) Show them Hide them
@@ -1,10 +1,119 b''
1 #!/usr/bin/env python
2 """Session object for building, serializing, sending, and receiving messages in
3 IPython. The Session object supports serialization, HMAC signatures, and
4 metadata on messages.
5
6 Also defined here are utilities for working with Sessions:
7 * A SessionFactory to be used as a base class for configurables that work with
8 Sessions.
9 * A Message object for convenience that allows attribute-access to the msg dict.
10
11 Authors:
12
13 * Min RK
14 * Brian Granger
15 * Fernando Perez
16 """
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2010-2011 The IPython Development Team
19 #
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
23
24 #-----------------------------------------------------------------------------
25 # Imports
26 #-----------------------------------------------------------------------------
27
28 import hmac
29 import logging
1 30 import os
2 import uuid
3 31 import pprint
32 import uuid
33 from datetime import datetime
34
35 try:
36 import cPickle
37 pickle = cPickle
38 except:
39 cPickle = None
40 import pickle
4 41
5 42 import zmq
43 from zmq.utils import jsonapi
44 from zmq.eventloop.ioloop import IOLoop
45 from zmq.eventloop.zmqstream import ZMQStream
46
47 from IPython.config.configurable import Configurable, LoggingConfigurable
48 from IPython.utils.importstring import import_item
49 from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
50 from IPython.utils.traitlets import CStr, Unicode, Bool, Any, Instance, Set
51
52 #-----------------------------------------------------------------------------
53 # utility functions
54 #-----------------------------------------------------------------------------
55
56 def squash_unicode(obj):
57 """coerce unicode back to bytestrings."""
58 if isinstance(obj,dict):
59 for key in obj.keys():
60 obj[key] = squash_unicode(obj[key])
61 if isinstance(key, unicode):
62 obj[squash_unicode(key)] = obj.pop(key)
63 elif isinstance(obj, list):
64 for i,v in enumerate(obj):
65 obj[i] = squash_unicode(v)
66 elif isinstance(obj, unicode):
67 obj = obj.encode('utf8')
68 return obj
69
70 #-----------------------------------------------------------------------------
71 # globals and defaults
72 #-----------------------------------------------------------------------------
73 key = 'on_unknown' if jsonapi.jsonmod.__name__ == 'jsonlib' else 'default'
74 json_packer = lambda obj: jsonapi.dumps(obj, **{key:date_default})
75 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
76
77 pickle_packer = lambda o: pickle.dumps(o,-1)
78 pickle_unpacker = pickle.loads
79
80 default_packer = json_packer
81 default_unpacker = json_unpacker
6 82
7 from zmq.utils import jsonapi as json
83
84 DELIM="<IDS|MSG>"
85
86 #-----------------------------------------------------------------------------
87 # Classes
88 #-----------------------------------------------------------------------------
89
90 class SessionFactory(LoggingConfigurable):
91 """The Base class for configurables that have a Session, Context, logger,
92 and IOLoop.
93 """
94
95 logname = Unicode('')
96 def _logname_changed(self, name, old, new):
97 self.log = logging.getLogger(new)
98
99 # not configurable:
100 context = Instance('zmq.Context')
101 def _context_default(self):
102 return zmq.Context.instance()
103
104 session = Instance('IPython.zmq.session.Session')
105
106 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
107 def _loop_default(self):
108 return IOLoop.instance()
109
110 def __init__(self, **kwargs):
111 super(SessionFactory, self).__init__(**kwargs)
112
113 if self.session is None:
114 # construct the session
115 self.session = Session(**kwargs)
116
8 117
9 118 class Message(object):
10 119 """A simple message object that maps dict keys to attributes.
@@ -14,7 +123,7 b' class Message(object):'
14 123
15 124 def __init__(self, msg_dict):
16 125 dct = self.__dict__
17 for k, v in msg_dict.iteritems():
126 for k, v in dict(msg_dict).iteritems():
18 127 if isinstance(v, dict):
19 128 v = Message(v)
20 129 dct[k] = v
@@ -36,13 +145,9 b' class Message(object):'
36 145 return self.__dict__[k]
37 146
38 147
39 def msg_header(msg_id, username, session):
40 return {
41 'msg_id' : msg_id,
42 'username' : username,
43 'session' : session
44 }
45
148 def msg_header(msg_id, msg_type, username, session):
149 date = datetime.now()
150 return locals()
46 151
47 152 def extract_header(msg_or_header):
48 153 """Given a message or header, return the header."""
@@ -63,109 +168,446 b' def extract_header(msg_or_header):'
63 168 h = dict(h)
64 169 return h
65 170
171 class Session(Configurable):
172 """Object for handling serialization and sending of messages.
173
174 The Session object handles building messages and sending them
175 with ZMQ sockets or ZMQStream objects. Objects can communicate with each
176 other over the network via Session objects, and only need to work with the
177 dict-based IPython message spec. The Session will handle
178 serialization/deserialization, security, and metadata.
179
180 Sessions support configurable serialiization via packer/unpacker traits,
181 and signing with HMAC digests via the key/keyfile traits.
182
183 Parameters
184 ----------
185
186 debug : bool
187 whether to trigger extra debugging statements
188 packer/unpacker : str : 'json', 'pickle' or import_string
189 importstrings for methods to serialize message parts. If just
190 'json' or 'pickle', predefined JSON and pickle packers will be used.
191 Otherwise, the entire importstring must be used.
192
193 The functions must accept at least valid JSON input, and output *bytes*.
194
195 For example, to use msgpack:
196 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
197 pack/unpack : callables
198 You can also set the pack/unpack callables for serialization directly.
199 session : bytes
200 the ID of this Session object. The default is to generate a new UUID.
201 username : unicode
202 username added to message headers. The default is to ask the OS.
203 key : bytes
204 The key used to initialize an HMAC signature. If unset, messages
205 will not be signed or checked.
206 keyfile : filepath
207 The file containing a key. If this is set, `key` will be initialized
208 to the contents of the file.
209
210 """
211
212 debug=Bool(False, config=True, help="""Debug output in the Session""")
213
214 packer = Unicode('json',config=True,
215 help="""The name of the packer for serializing messages.
216 Should be one of 'json', 'pickle', or an import name
217 for a custom callable serializer.""")
218 def _packer_changed(self, name, old, new):
219 if new.lower() == 'json':
220 self.pack = json_packer
221 self.unpack = json_unpacker
222 elif new.lower() == 'pickle':
223 self.pack = pickle_packer
224 self.unpack = pickle_unpacker
225 else:
226 self.pack = import_item(str(new))
66 227
67 class Session(object):
68
69 def __init__(self, username=os.environ.get('USER','username'), session=None):
70 self.username = username
71 if session is None:
72 self.session = str(uuid.uuid4())
228 unpacker = Unicode('json', config=True,
229 help="""The name of the unpacker for unserializing messages.
230 Only used with custom functions for `packer`.""")
231 def _unpacker_changed(self, name, old, new):
232 if new.lower() == 'json':
233 self.pack = json_packer
234 self.unpack = json_unpacker
235 elif new.lower() == 'pickle':
236 self.pack = pickle_packer
237 self.unpack = pickle_unpacker
73 238 else:
74 self.session = session
75 self.msg_id = 0
239 self.unpack = import_item(str(new))
240
241 session = CStr('', config=True,
242 help="""The UUID identifying this session.""")
243 def _session_default(self):
244 return bytes(uuid.uuid4())
245
246 username = Unicode(os.environ.get('USER','username'), config=True,
247 help="""Username for the Session. Default is your system username.""")
248
249 # message signature related traits:
250 key = CStr('', config=True,
251 help="""execution key, for extra authentication.""")
252 def _key_changed(self, name, old, new):
253 if new:
254 self.auth = hmac.HMAC(new)
255 else:
256 self.auth = None
257 auth = Instance(hmac.HMAC)
258 digest_history = Set()
259
260 keyfile = Unicode('', config=True,
261 help="""path to file containing execution key.""")
262 def _keyfile_changed(self, name, old, new):
263 with open(new, 'rb') as f:
264 self.key = f.read().strip()
76 265
77 def msg_header(self):
78 h = msg_header(self.msg_id, self.username, self.session)
79 self.msg_id += 1
80 return h
266 pack = Any(default_packer) # the actual packer function
267 def _pack_changed(self, name, old, new):
268 if not callable(new):
269 raise TypeError("packer must be callable, not %s"%type(new))
270
271 unpack = Any(default_unpacker) # the actual packer function
272 def _unpack_changed(self, name, old, new):
273 # unpacker is not checked - it is assumed to be
274 if not callable(new):
275 raise TypeError("unpacker must be callable, not %s"%type(new))
81 276
82 def msg(self, msg_type, content=None, parent=None):
83 """Construct a standard-form message, with a given type, content, and parent.
277 def __init__(self, **kwargs):
278 """create a Session object
84 279
85 NOT to be called directly.
280 Parameters
281 ----------
282
283 debug : bool
284 whether to trigger extra debugging statements
285 packer/unpacker : str : 'json', 'pickle' or import_string
286 importstrings for methods to serialize message parts. If just
287 'json' or 'pickle', predefined JSON and pickle packers will be used.
288 Otherwise, the entire importstring must be used.
289
290 The functions must accept at least valid JSON input, and output
291 *bytes*.
292
293 For example, to use msgpack:
294 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
295 pack/unpack : callables
296 You can also set the pack/unpack callables for serialization
297 directly.
298 session : bytes
299 the ID of this Session object. The default is to generate a new
300 UUID.
301 username : unicode
302 username added to message headers. The default is to ask the OS.
303 key : bytes
304 The key used to initialize an HMAC signature. If unset, messages
305 will not be signed or checked.
306 keyfile : filepath
307 The file containing a key. If this is set, `key` will be
308 initialized to the contents of the file.
86 309 """
310 super(Session, self).__init__(**kwargs)
311 self._check_packers()
312 self.none = self.pack({})
313
314 @property
315 def msg_id(self):
316 """always return new uuid"""
317 return str(uuid.uuid4())
318
319 def _check_packers(self):
320 """check packers for binary data and datetime support."""
321 pack = self.pack
322 unpack = self.unpack
323
324 # check simple serialization
325 msg = dict(a=[1,'hi'])
326 try:
327 packed = pack(msg)
328 except Exception:
329 raise ValueError("packer could not serialize a simple message")
330
331 # ensure packed message is bytes
332 if not isinstance(packed, bytes):
333 raise ValueError("message packed to %r, but bytes are required"%type(packed))
334
335 # check that unpack is pack's inverse
336 try:
337 unpacked = unpack(packed)
338 except Exception:
339 raise ValueError("unpacker could not handle the packer's output")
340
341 # check datetime support
342 msg = dict(t=datetime.now())
343 try:
344 unpacked = unpack(pack(msg))
345 except Exception:
346 self.pack = lambda o: pack(squash_dates(o))
347 self.unpack = lambda s: extract_dates(unpack(s))
348
349 def msg_header(self, msg_type):
350 return msg_header(self.msg_id, msg_type, self.username, self.session)
351
352 def msg(self, msg_type, content=None, parent=None, subheader=None):
87 353 msg = {}
88 msg['header'] = self.msg_header()
354 msg['header'] = self.msg_header(msg_type)
355 msg['msg_id'] = msg['header']['msg_id']
89 356 msg['parent_header'] = {} if parent is None else extract_header(parent)
90 357 msg['msg_type'] = msg_type
91 358 msg['content'] = {} if content is None else content
359 sub = {} if subheader is None else subheader
360 msg['header'].update(sub)
92 361 return msg
93 362
94 def send(self, socket, msg_or_type, content=None, parent=None, ident=None):
95 """send a message via a socket, using a uniform message pattern.
96
97 Parameters
98 ----------
99 socket : zmq.Socket
100 The socket on which to send.
101 msg_or_type : Message/dict or str
102 if str : then a new message will be constructed from content,parent
103 if Message/dict : then content and parent are ignored, and the message
104 is sent. This is only for use when sending a Message for a second time.
105 content : dict, optional
106 The contents of the message
107 parent : dict, optional
108 The parent header, or parent message, of this message
109 ident : bytes, optional
110 The zmq.IDENTITY prefix of the destination.
111 Only for use on certain socket types.
363 def sign(self, msg):
364 """Sign a message with HMAC digest. If no auth, return b''."""
365 if self.auth is None:
366 return b''
367 h = self.auth.copy()
368 for m in msg:
369 h.update(m)
370 return h.hexdigest()
371
372 def serialize(self, msg, ident=None):
373 """Serialize the message components to bytes.
112 374
113 375 Returns
114 376 -------
115 msg : dict
116 The message, as constructed by self.msg(msg_type,content,parent)
377
378 list of bytes objects
379
117 380 """
118 if isinstance(msg_or_type, (Message, dict)):
119 msg = dict(msg_or_type)
381 content = msg.get('content', {})
382 if content is None:
383 content = self.none
384 elif isinstance(content, dict):
385 content = self.pack(content)
386 elif isinstance(content, bytes):
387 # content is already packed, as in a relayed message
388 pass
389 elif isinstance(content, unicode):
390 # should be bytes, but JSON often spits out unicode
391 content = content.encode('utf8')
120 392 else:
121 msg = self.msg(msg_or_type, content, parent)
122 if ident is not None:
123 socket.send(ident, zmq.SNDMORE)
124 socket.send_json(msg)
125 return msg
393 raise TypeError("Content incorrect type: %s"%type(content))
394
395 real_message = [self.pack(msg['header']),
396 self.pack(msg['parent_header']),
397 content
398 ]
399
400 to_send = []
401
402 if isinstance(ident, list):
403 # accept list of idents
404 to_send.extend(ident)
405 elif ident is not None:
406 to_send.append(ident)
407 to_send.append(DELIM)
408
409 signature = self.sign(real_message)
410 to_send.append(signature)
411
412 to_send.extend(real_message)
126 413
127 def recv(self, socket, mode=zmq.NOBLOCK):
128 """recv a message on a socket.
414 return to_send
129 415
130 Receive an optionally identity-prefixed message, as sent via session.send().
416 def send(self, stream, msg_or_type, content=None, parent=None, ident=None,
417 buffers=None, subheader=None, track=False):
418 """Build and send a message via stream or socket.
131 419
132 420 Parameters
133 421 ----------
134 422
135 socket : zmq.Socket
136 The socket on which to recv a message.
137 mode : int, optional
138 the mode flag passed to socket.recv
139 default: zmq.NOBLOCK
423 stream : zmq.Socket or ZMQStream
424 the socket-like object used to send the data
425 msg_or_type : str or Message/dict
426 Normally, msg_or_type will be a msg_type unless a message is being
427 sent more than once.
428
429 content : dict or None
430 the content of the message (ignored if msg_or_type is a message)
431 parent : Message or dict or None
432 the parent or parent header describing the parent of this message
433 ident : bytes or list of bytes
434 the zmq.IDENTITY routing path
435 subheader : dict or None
436 extra header keys for this message's header
437 buffers : list or None
438 the already-serialized buffers to be appended to the message
439 track : bool
440 whether to track. Only for use with Sockets,
441 because ZMQStream objects cannot track messages.
140 442
141 443 Returns
142 444 -------
143 (ident,msg) : tuple
144 always length 2. If no message received, then return is (None,None)
145 ident : bytes or None
146 the identity prefix is there was one, None otherwise.
147 msg : dict or None
148 The actual message. If mode==zmq.NOBLOCK and no message was waiting,
149 it will be None.
445 msg : message dict
446 the constructed message
447 (msg,tracker) : (message dict, MessageTracker)
448 if track=True, then a 2-tuple will be returned,
449 the first element being the constructed
450 message, and the second being the MessageTracker
451
150 452 """
453
454 if not isinstance(stream, (zmq.Socket, ZMQStream)):
455 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
456 elif track and isinstance(stream, ZMQStream):
457 raise TypeError("ZMQStream cannot track messages")
458
459 if isinstance(msg_or_type, (Message, dict)):
460 # we got a Message, not a msg_type
461 # don't build a new Message
462 msg = msg_or_type
463 else:
464 msg = self.msg(msg_or_type, content, parent, subheader)
465
466 buffers = [] if buffers is None else buffers
467 to_send = self.serialize(msg, ident)
468 flag = 0
469 if buffers:
470 flag = zmq.SNDMORE
471 _track = False
472 else:
473 _track=track
474 if track:
475 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
476 else:
477 tracker = stream.send_multipart(to_send, flag, copy=False)
478 for b in buffers[:-1]:
479 stream.send(b, flag, copy=False)
480 if buffers:
481 if track:
482 tracker = stream.send(buffers[-1], copy=False, track=track)
483 else:
484 tracker = stream.send(buffers[-1], copy=False)
485
486 # omsg = Message(msg)
487 if self.debug:
488 pprint.pprint(msg)
489 pprint.pprint(to_send)
490 pprint.pprint(buffers)
491
492 msg['tracker'] = tracker
493
494 return msg
495
496 def send_raw(self, stream, msg, flags=0, copy=True, ident=None):
497 """Send a raw message via ident path.
498
499 Parameters
500 ----------
501 msg : list of sendable buffers"""
502 to_send = []
503 if isinstance(ident, bytes):
504 ident = [ident]
505 if ident is not None:
506 to_send.extend(ident)
507
508 to_send.append(DELIM)
509 to_send.append(self.sign(msg))
510 to_send.extend(msg)
511 stream.send_multipart(msg, flags, copy=copy)
512
513 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
514 """receives and unpacks a message
515 returns [idents], msg"""
516 if isinstance(socket, ZMQStream):
517 socket = socket.socket
151 518 try:
152 519 msg = socket.recv_multipart(mode)
153 except zmq.ZMQError, e:
520 except zmq.ZMQError as e:
154 521 if e.errno == zmq.EAGAIN:
155 522 # We can convert EAGAIN to None as we know in this case
156 # recv_json won't return None.
523 # recv_multipart won't return None.
157 524 return None,None
158 525 else:
159 526 raise
160 if len(msg) == 1:
161 ident=None
162 msg = msg[0]
163 elif len(msg) == 2:
164 ident, msg = msg
527 # split multipart message into identity list and message dict
528 # invalid large messages can cause very expensive string comparisons
529 idents, msg = self.feed_identities(msg, copy)
530 try:
531 return idents, self.unpack_message(msg, content=content, copy=copy)
532 except Exception as e:
533 print (idents, msg)
534 # TODO: handle it
535 raise e
536
537 def feed_identities(self, msg, copy=True):
538 """feed until DELIM is reached, then return the prefix as idents and
539 remainder as msg. This is easily broken by setting an IDENT to DELIM,
540 but that would be silly.
541
542 Parameters
543 ----------
544 msg : a list of Message or bytes objects
545 the message to be split
546 copy : bool
547 flag determining whether the arguments are bytes or Messages
548
549 Returns
550 -------
551 (idents,msg) : two lists
552 idents will always be a list of bytes - the indentity prefix
553 msg will be a list of bytes or Messages, unchanged from input
554 msg should be unpackable via self.unpack_message at this point.
555 """
556 if copy:
557 idx = msg.index(DELIM)
558 return msg[:idx], msg[idx+1:]
165 559 else:
166 raise ValueError("Got message with length > 2, which is invalid")
560 failed = True
561 for idx,m in enumerate(msg):
562 if m.bytes == DELIM:
563 failed = False
564 break
565 if failed:
566 raise ValueError("DELIM not in msg")
567 idents, msg = msg[:idx], msg[idx+1:]
568 return [m.bytes for m in idents], msg
569
570 def unpack_message(self, msg, content=True, copy=True):
571 """Return a message object from the format
572 sent by self.send.
573
574 Parameters:
575 -----------
167 576
168 return ident, json.loads(msg)
577 content : bool (True)
578 whether to unpack the content dict (True),
579 or leave it serialized (False)
580
581 copy : bool (True)
582 whether to return the bytes (True),
583 or the non-copying Message object in each place (False)
584
585 """
586 minlen = 4
587 message = {}
588 if not copy:
589 for i in range(minlen):
590 msg[i] = msg[i].bytes
591 if self.auth is not None:
592 signature = msg[0]
593 if signature in self.digest_history:
594 raise ValueError("Duplicate Signature: %r"%signature)
595 self.digest_history.add(signature)
596 check = self.sign(msg[1:4])
597 if not signature == check:
598 raise ValueError("Invalid Signature: %r"%signature)
599 if not len(msg) >= minlen:
600 raise TypeError("malformed message, must have at least %i elements"%minlen)
601 message['header'] = self.unpack(msg[1])
602 message['msg_type'] = message['header']['msg_type']
603 message['parent_header'] = self.unpack(msg[2])
604 if content:
605 message['content'] = self.unpack(msg[3])
606 else:
607 message['content'] = msg[3]
608
609 message['buffers'] = msg[4:]
610 return message
169 611
170 612 def test_msg2obj():
171 613 am = dict(x=1)
@@ -182,3 +624,4 b' def test_msg2obj():'
182 624 am2 = dict(ao)
183 625 assert am['x'] == am2['x']
184 626 assert am['y']['z'] == am2['y']['z']
627
@@ -35,6 +35,6 b' def teardown():'
35 35 # Actual tests
36 36
37 37 def test_execute():
38 KM.xreq_channel.execute(code='x=1')
39 KM.xreq_channel.execute(code='print 1')
38 KM.shell_channel.execute(code='x=1')
39 KM.shell_channel.execute(code='print 1')
40 40
@@ -17,14 +17,14 b' import zmq'
17 17
18 18 from zmq.tests import BaseZMQTestCase
19 19 from zmq.eventloop.zmqstream import ZMQStream
20 # from IPython.zmq.tests import SessionTestCase
21 from IPython.parallel import streamsession as ss
20
21 from IPython.zmq import session as ss
22 22
23 23 class SessionTestCase(BaseZMQTestCase):
24 24
25 25 def setUp(self):
26 26 BaseZMQTestCase.setUp(self)
27 self.session = ss.StreamSession()
27 self.session = ss.Session()
28 28
29 29 class TestSession(SessionTestCase):
30 30
@@ -42,19 +42,19 b' class TestSession(SessionTestCase):'
42 42
43 43
44 44 def test_args(self):
45 """initialization arguments for StreamSession"""
45 """initialization arguments for Session"""
46 46 s = self.session
47 47 self.assertTrue(s.pack is ss.default_packer)
48 48 self.assertTrue(s.unpack is ss.default_unpacker)
49 49 self.assertEquals(s.username, os.environ.get('USER', 'username'))
50 50
51 s = ss.StreamSession(username=None)
51 s = ss.Session()
52 52 self.assertEquals(s.username, os.environ.get('USER', 'username'))
53 53
54 self.assertRaises(TypeError, ss.StreamSession, packer='hi')
55 self.assertRaises(TypeError, ss.StreamSession, unpacker='hi')
54 self.assertRaises(TypeError, ss.Session, pack='hi')
55 self.assertRaises(TypeError, ss.Session, unpack='hi')
56 56 u = str(uuid.uuid4())
57 s = ss.StreamSession(username='carrot', session=u)
57 s = ss.Session(username='carrot', session=u)
58 58 self.assertEquals(s.session, u)
59 59 self.assertEquals(s.username, 'carrot')
60 60
@@ -56,7 +56,7 b' class Bar(Configurable):'
56 56
57 57 class MyApp(Application):
58 58
59 app_name = Unicode(u'myapp')
59 name = Unicode(u'myapp')
60 60 running = Bool(False, config=True,
61 61 help="Is the app running?")
62 62 classes = List([Bar, Foo])
@@ -79,17 +79,22 b' class MyApp(Application):'
79 79 # Pass config to other classes for them to inherit the config.
80 80 self.bar = Bar(config=self.config)
81 81
82 def initialize(self, argv=None):
83 self.parse_command_line(argv)
84 if self.config_file:
85 self.load_config_file(self.config_file)
86 self.init_foo()
87 self.init_bar()
88
89 def start(self):
90 print "app.config:"
91 print self.config
82 92
83 93
84 94 def main():
85 95 app = MyApp()
86 app.parse_command_line()
87 if app.config_file:
88 app.load_config_file(app.config_file)
89 app.init_foo()
90 app.init_bar()
91 print "app.config:"
92 print app.config
96 app.initialize()
97 app.start()
93 98
94 99
95 100 if __name__ == "__main__":
@@ -46,7 +46,7 b' There are two ways you can tell IPython to use your extension:'
46 46 To load an extension called :file:`myextension.py` add the following logic
47 47 to your configuration file::
48 48
49 c.Global.extensions = [
49 c.InteractiveShellApp.extensions = [
50 50 'myextension'
51 51 ]
52 52
@@ -15,63 +15,74 b' can configure the application. A sample is provided in'
15 15 :mod:`IPython.config.default.ipython_config`. Simply copy this file to your
16 16 :ref:`IPython directory <ipython_dir>` to start using it.
17 17
18 Most configuration attributes that this file accepts are associated with
19 classes that are subclasses of :class:`~IPython.core.component.Component`.
18 Most configuration attributes that this file accepts are associated with classes
19 that are subclasses of :class:`~IPython.config.configurable.Configurable`.
20 20
21 A few configuration attributes are not associated with a particular
22 :class:`~IPython.core.component.Component` subclass. These are application
23 wide configuration attributes and are stored in the ``Global``
24 sub-configuration section. We begin with a description of these
25 attributes.
21 Applications themselves are Configurable as well, so we will start with some
22 application-level config.
26 23
27 Global configuration
28 ====================
24 Application-level configuration
25 ===============================
29 26
30 27 Assuming that your configuration file has the following at the top::
31 28
32 29 c = get_config()
33 30
34 the following attributes can be set in the ``Global`` section.
31 the following attributes are set application-wide:
35 32
36 :attr:`c.Global.display_banner`
33 terminal IPython-only flags:
34
35 :attr:`c.TerminalIPythonApp.display_banner`
37 36 A boolean that determined if the banner is printer when :command:`ipython`
38 37 is started.
39 38
40 :attr:`c.Global.classic`
39 :attr:`c.TerminalIPythonApp.classic`
41 40 A boolean that determines if IPython starts in "classic" mode. In this
42 41 mode, the prompts and everything mimic that of the normal :command:`python`
43 42 shell
44 43
45 :attr:`c.Global.nosep`
44 :attr:`c.TerminalIPythonApp.nosep`
46 45 A boolean that determines if there should be no blank lines between
47 46 prompts.
48 47
49 :attr:`c.Global.log_level`
48 :attr:`c.Application.log_level`
50 49 An integer that sets the detail of the logging level during the startup
51 50 of :command:`ipython`. The default is 30 and the possible values are
52 51 (0, 10, 20, 30, 40, 50). Higher is quieter and lower is more verbose.
52 This can also be set by the name of the logging level, e.g. INFO=20,
53 WARN=30.
54
55 Some options, such as extensions and startup code, can be set for any
56 application that starts an
57 :class:`~IPython.core.interactiveshell.InteractiveShell`. These apps are
58 subclasses of :class:`~IPython.core.shellapp.InteractiveShellApp`. Since
59 subclasses inherit configuration, setting a trait of
60 :attr:`c.InteractiveShellApp` will affect all IPython applications, but if you
61 want terminal IPython and the QtConsole to have different values, you can set
62 them via :attr:`c.TerminalIPythonApp` and :attr:`c.IPKernelApp` respectively.
63
53 64
54 :attr:`c.Global.extensions`
65 :attr:`c.InteractiveShellApp.extensions`
55 66 A list of strings, each of which is an importable IPython extension. An
56 67 IPython extension is a regular Python module or package that has a
57 68 :func:`load_ipython_extension(ip)` method. This method gets called when
58 69 the extension is loaded with the currently running
59 :class:`~IPython.core.iplib.InteractiveShell` as its only argument. You
60 can put your extensions anywhere they can be imported but we add the
61 :file:`extensions` subdirectory of the ipython directory to ``sys.path``
62 during extension loading, so you can put them there as well. Extensions
63 are not executed in the user's interactive namespace and they must be pure
64 Python code. Extensions are the recommended way of customizing
70 :class:`~IPython.core.interactiveshell.InteractiveShell` as its only
71 argument. You can put your extensions anywhere they can be imported but we
72 add the :file:`extensions` subdirectory of the ipython directory to
73 ``sys.path`` during extension loading, so you can put them there as well.
74 Extensions are not executed in the user's interactive namespace and they
75 must be pure Python code. Extensions are the recommended way of customizing
65 76 :command:`ipython`. Extensions can provide an
66 77 :func:`unload_ipython_extension` that will be called when the extension is
67 78 unloaded.
68 79
69 :attr:`c.Global.exec_lines`
80 :attr:`c.InteractiveShellApp.exec_lines`
70 81 A list of strings, each of which is Python code that is run in the user's
71 82 namespace after IPython start. These lines can contain full IPython syntax
72 83 with magics, etc.
73 84
74 :attr:`c.Global.exec_files`
85 :attr:`c.InteractiveShellApp.exec_files`
75 86 A list of strings, each of which is the full pathname of a ``.py`` or
76 87 ``.ipy`` file that will be executed as IPython starts. These files are run
77 88 in IPython in the user's namespace. Files with a ``.py`` extension need to
@@ -85,7 +96,7 b' Classes that can be configured'
85 96 The following classes can also be configured in the configuration file for
86 97 :command:`ipython`:
87 98
88 * :class:`~IPython.core.iplib.InteractiveShell`
99 * :class:`~IPython.core.interactiveshell.InteractiveShell`
89 100
90 101 * :class:`~IPython.core.prefilter.PrefilterManager`
91 102
@@ -105,16 +116,16 b' attributes::'
105 116 # sample ipython_config.py
106 117 c = get_config()
107 118
108 c.Global.display_banner = True
109 c.Global.log_level = 20
110 c.Global.extensions = [
119 c.IPythonTerminalApp.display_banner = True
120 c.InteractiveShellApp.log_level = 20
121 c.InteractiveShellApp.extensions = [
111 122 'myextension'
112 123 ]
113 c.Global.exec_lines = [
124 c.InteractiveShellApp.exec_lines = [
114 125 'import numpy',
115 126 'import scipy'
116 127 ]
117 c.Global.exec_files = [
128 c.InteractiveShellApp.exec_files = [
118 129 'mycode.py',
119 130 'fancy.ipy'
120 131 ]
@@ -18,8 +18,8 b' met our requirements.'
18 18 your old :file:`ipythonrc` or :file:`ipy_user_conf.py` configuration files
19 19 to the new system. Read on for information on how to do this.
20 20
21 The discussion that follows is focused on teaching user's how to configure
22 IPython to their liking. Developer's who want to know more about how they
21 The discussion that follows is focused on teaching users how to configure
22 IPython to their liking. Developers who want to know more about how they
23 23 can enable their objects to take advantage of the configuration system
24 24 should consult our :ref:`developer guide <developer_guide>`
25 25
@@ -37,15 +37,19 b' Configuration object: :class:`~IPython.config.loader.Config`'
37 37 are smart. They know how to merge themselves with other configuration
38 38 objects and they automatically create sub-configuration objects.
39 39
40 Application: :class:`~IPython.core.application.Application`
40 Application: :class:`~IPython.config.application.Application`
41 41 An application is a process that does a specific job. The most obvious
42 42 application is the :command:`ipython` command line program. Each
43 application reads a *single* configuration file and command line options
43 application reads *one or more* configuration files and a single set of
44 command line options
44 45 and then produces a master configuration object for the application. This
45 46 configuration object is then passed to the configurable objects that the
46 47 application creates. These configurable objects implement the actual logic
47 48 of the application and know how to configure themselves given the
48 49 configuration object.
50
51 Applications always have a `log` attribute that is a configured Logger.
52 This allows centralized logging configuration per-application.
49 53
50 54 Component: :class:`~IPython.config.configurable.Configurable`
51 55 A configurable is a regular Python class that serves as a base class for
@@ -64,6 +68,25 b' Component: :class:`~IPython.config.configurable.Configurable`'
64 68 these subclasses has its own configuration information that controls how
65 69 instances are created.
66 70
71 Singletons: :class:`~IPython.config.configurable.SingletonConfigurable`
72 Any object for which there is a single canonical instance. These are
73 just like Configurables, except they have a class method
74 :meth:`~IPython.config.configurable.SingletonConfigurable.instance`,
75 that returns the current active instance (or creates one if it
76 does not exist). Examples of singletons include
77 :class:`~IPython.config.application.Application`s and
78 :class:`~IPython.core.interactiveshell.InteractiveShell`. This lets
79 objects easily connect to the current running Application without passing
80 objects around everywhere. For instance, to get the current running
81 Application instance, simply do: ``app = Application.instance()``.
82
83
84 .. note::
85
86 Singletons are not strictly enforced - you can have many instances
87 of a given singleton class, but the :meth:`instance` method will always
88 return the same one.
89
67 90 Having described these main concepts, we can now state the main idea in our
68 91 configuration system: *"configuration" allows the default values of class
69 92 attributes to be controlled on a class by class basis*. Thus all instances of
@@ -106,7 +129,7 b' subclass::'
106 129 from IPython.utils.traitlets import Int, Float, Str, Bool
107 130
108 131 class MyClass(Configurable):
109 name = Str('defaultname', config=True)
132 name = Unicode(u'defaultname', config=True)
110 133 ranking = Int(0, config=True)
111 134 value = Float(99.0)
112 135 # The rest of the class implementation would go here..
@@ -197,6 +220,15 b' search path for sub-configuration files is inherited from that of the parent.'
197 220 Thus, you can typically put the two in the same directory and everything will
198 221 just work.
199 222
223 You can also load configuration files by profile, for instance:
224
225 .. sourcecode:: python
226
227 load_subconfig('ipython_config.py', profile='default')
228
229 to inherit your default configuration as a starting point.
230
231
200 232 Class based configuration inheritance
201 233 =====================================
202 234
@@ -241,11 +273,12 b' This class hierarchy and configuration file accomplishes the following:'
241 273 Configuration file location
242 274 ===========================
243 275
244 So where should you put your configuration files? By default, all IPython
245 applications look in the so called "IPython directory". The location of
246 this directory is determined by the following algorithm:
276 So where should you put your configuration files? IPython uses "profiles" for
277 configuration, and by default, all profiles will be stored in the so called
278 "IPython directory". The location of this directory is determined by the
279 following algorithm:
247 280
248 * If the ``--ipython-dir`` command line flag is given, its value is used.
281 * If the ``ipython_dir`` command line flag is given, its value is used.
249 282
250 283 * If not, the value returned by :func:`IPython.utils.path.get_ipython_dir`
251 284 is used. This function will first look at the :envvar:`IPYTHON_DIR`
@@ -263,45 +296,149 b' For most users, the default value will simply be something like'
263 296 :file:`$HOME/.config/ipython` on Linux, or :file:`$HOME/.ipython`
264 297 elsewhere.
265 298
266 Once the location of the IPython directory has been determined, you need to
267 know what filename to use for the configuration file. The basic idea is that
268 each application has its own default configuration filename. The default named
269 used by the :command:`ipython` command line program is
270 :file:`ipython_config.py`. This value can be overriden by the ``-config_file``
271 command line flag. A sample :file:`ipython_config.py` file can be found
272 in :mod:`IPython.config.default.ipython_config.py`. Simple copy it to your
273 IPython directory to begin using it.
299 Once the location of the IPython directory has been determined, you need to know
300 which profile you are using. For users with a single configuration, this will
301 simply be 'default', and will be located in
302 :file:`<IPYTHON_DIR>/profile_default`.
303
304 The next thing you need to know is what to call your configuration file. The
305 basic idea is that each application has its own default configuration filename.
306 The default named used by the :command:`ipython` command line program is
307 :file:`ipython_config.py`, and *all* IPython applications will use this file.
308 Other applications, such as the parallel :command:`ipcluster` scripts or the
309 QtConsole will load their own config files *after* :file:`ipython_config.py`. To
310 load a particular configuration file instead of the default, the name can be
311 overridden by the ``config_file`` command line flag.
312
313 To generate the default configuration files, do::
314
315 $> ipython profile create
316
317 and you will have a default :file:`ipython_config.py` in your IPython directory
318 under :file:`profile_default`. If you want the default config files for the
319 :mod:`IPython.parallel` applications, add ``--parallel`` to the end of the
320 command-line args.
274 321
275 322 .. _Profiles:
276 323
277 324 Profiles
278 325 ========
279 326
280 A profile is simply a configuration file that follows a simple naming
281 convention and can be loaded using a simplified syntax. The idea is
282 that users often want to maintain a set of configuration files for different
283 purposes: one for doing numerical computing with NumPy and SciPy and
284 another for doing symbolic computing with SymPy. Profiles make it easy
285 to keep a separate configuration file for each of these purposes.
327 A profile is a directory containing configuration and runtime files, such as
328 logs, connection info for the parallel apps, and your IPython command history.
329
330 The idea is that users often want to maintain a set of configuration files for
331 different purposes: one for doing numerical computing with NumPy and SciPy and
332 another for doing symbolic computing with SymPy. Profiles make it easy to keep a
333 separate configuration files, logs, and histories for each of these purposes.
286 334
287 335 Let's start by showing how a profile is used:
288 336
289 337 .. code-block:: bash
290 338
291 $ ipython -p sympy
339 $ ipython profile=sympy
340
341 This tells the :command:`ipython` command line program to get its configuration
342 from the "sympy" profile. The file names for various profiles do not change. The
343 only difference is that profiles are named in a special way. In the case above,
344 the "sympy" profile means looking for :file:`ipython_config.py` in :file:`<IPYTHON_DIR>/profile_sympy`.
345
346 The general pattern is this: simply create a new profile with:
347
348 .. code-block:: bash
349
350 ipython profile create <name>
351
352 which adds a directory called ``profile_<name>`` to your IPython directory. Then
353 you can load this profile by adding ``profile=<name>`` to your command line
354 options. Profiles are supported by all IPython applications.
355
356 IPython ships with some sample profiles in :file:`IPython/config/profile`. If
357 you create profiles with the name of one of our shipped profiles, these config
358 files will be copied over instead of starting with the automatically generated
359 config files.
360
361 .. _commandline:
362
363 Command-line arguments
364 ======================
365
366 IPython exposes *all* configurable options on the command-line. The command-line
367 arguments are generated from the Configurable traits of the classes associated
368 with a given Application. Configuring IPython from the command-line may look
369 very similar to an IPython config file
370
371 IPython applications use a parser called
372 :class:`~IPython.config.loader.KeyValueLoader` to load values into a Config
373 object. Values are assigned in much the same way as in a config file:
374
375 .. code-block:: bash
376
377 $> ipython InteractiveShell.use_readline=False BaseIPythonApplication.profile='myprofile'
378
379 Is the same as adding:
380
381 .. sourcecode:: python
382
383 c.InteractiveShell.use_readline=False
384 c.BaseIPythonApplication.profile='myprofile'
385
386 to your config file. Key/Value arguments *always* take a value, separated by '='
387 and no spaces.
388
389 Aliases
390 -------
391
392 For convenience, applications have a mapping of commonly
393 used traits, so you don't have to specify the whole class name. For these **aliases**, the class need not be specified:
394
395 .. code-block:: bash
396
397 $> ipython profile='myprofile'
398 # is equivalent to
399 $> ipython BaseIPythonApplication.profile='myprofile'
400
401 Flags
402 -----
403
404 Applications can also be passed **flags**. Flags are options that take no
405 arguments, and are always prefixed with ``--``. They are simply wrappers for
406 setting one or more configurables with predefined values, often True/False.
407
408 For instance:
409
410 .. code-block:: bash
411
412 $> ipcontroller --debug
413 # is equivalent to
414 $> ipcontroller Application.log_level=DEBUG
415 # and
416 $> ipython --pylab
417 # is equivalent to
418 $> ipython pylab=auto
419
420 Subcommands
421 -----------
422
423
424 Some IPython applications have **subcommands**. Subcommands are modeled after
425 :command:`git`, and are called with the form :command:`command subcommand
426 [...args]`. Currently, the QtConsole is a subcommand of terminal IPython:
427
428 .. code-block:: bash
429
430 $> ipython qtconsole profile=myprofile
431
432 and :command:`ipcluster` is simply a wrapper for its various subcommands (start,
433 stop, engines).
434
435 .. code-block:: bash
436
437 $> ipcluster start profile=myprofile n=4
292 438
293 This tells the :command:`ipython` command line program to get its
294 configuration from the "sympy" profile. The search path for profiles is the
295 same as that of regular configuration files. The only difference is that
296 profiles are named in a special way. In the case above, the "sympy" profile
297 would need to have the name :file:`ipython_config_sympy.py`.
298 439
299 The general pattern is this: simply add ``_profilename`` to the end of the
300 normal configuration file name. Then load the profile by adding ``-p
301 profilename`` to your command line options.
440 To see a list of the available aliases, flags, and subcommands for an IPython application, simply pass ``-h`` or ``--help``. And to see the full list of configurable options (*very* long), pass ``--help-all``.
302 441
303 IPython ships with some sample profiles in :mod:`IPython.config.profile`.
304 Simply copy these to your IPython directory to begin using them.
305 442
306 443 Design requirements
307 444 ===================
@@ -23,4 +23,3 b" IPython developer's guide"
23 23 ipgraph.txt
24 24 ipython_qt.txt
25 25 ipythonzmq.txt
26 parallelzmq.txt
@@ -303,11 +303,10 b' pyzmq'
303 303 IPython 0.11 introduced some new functionality, including a two-process
304 304 execution model using ZeroMQ for communication [ZeroMQ]_. The Python bindings
305 305 to ZeroMQ are found in the pyzmq project, which is easy_install-able once you
306 have ZeroMQ installed (or even if you don't).
306 have ZeroMQ installed. If you are on Python 2.6 or 2.7 on OSX, or 2.7 on Windows,
307 pyzmq has eggs that include ZeroMQ itself.
307 308
308 IPython.zmq depends on pyzmq >= 2.0.10.1, but IPython.parallel requires the more
309 recent 2.1.4. 2.1.4 also has binary releases for OSX and Windows, that do not
310 require prior installation of libzmq.
309 IPython.zmq depends on pyzmq >= 2.1.4.
311 310
312 311 Dependencies for ipython-qtconsole (new GUI)
313 312 ============================================
@@ -59,16 +59,16 b' input and the drawing eventloop.'
59 59 ****************
60 60
61 61 An additional function, :func:`pastefig`, will be added to the global namespace if you
62 specify the ``--pylab`` argument. This takes the active figures in matplotlib, and embeds
62 specify the ``pylab`` argument. This takes the active figures in matplotlib, and embeds
63 63 them in your document. This is especially useful for saving_ your work.
64 64
65 65 .. _inline:
66 66
67 ``--pylab inline``
67 ``pylab=inline``
68 68 ******************
69 69
70 70 If you want to have all of your figures embedded in your session, instead of calling
71 :func:`pastefig`, you can specify ``--pylab inline``, and each time you make a plot, it
71 :func:`pastefig`, you can specify ``pylab=inline``, and each time you make a plot, it
72 72 will show up in your document, as if you had called :func:`pastefig`.
73 73
74 74
@@ -89,8 +89,8 b' context menu.'
89 89
90 90 .. Note::
91 91
92 Saving is only available to richtext Qt widgets, so make sure you start ipqt with the
93 ``--rich`` flag, or with ``--pylab``, which always uses a richtext widget.
92 Saving is only available to richtext Qt widgets, which are used by default, but
93 if you pass the ``--plain`` flag, saving will not be available to you.
94 94
95 95
96 96 See these examples of :download:`png/html<figs/jn.html>` and :download:`svg/xhtml
@@ -101,23 +101,22 b' Colors and Highlighting'
101 101 =======================
102 102
103 103 Terminal IPython has always had some coloring, but never syntax highlighting. There are a
104 few simple color choices, specified by the ``--colors`` flag or ``%colors`` magic:
104 few simple color choices, specified by the ``colors`` flag or ``%colors`` magic:
105 105
106 106 * LightBG for light backgrounds
107 107 * Linux for dark backgrounds
108 108 * NoColor for a simple colorless terminal
109 109
110 The Qt widget has full support for the ``--colors`` flag, but adds new, more intuitive
111 aliases for the colors (the old names still work): dark=Linux, light=LightBG, bw=NoColor.
110 The Qt widget has full support for the ``colors`` flag used in the terminal shell.
112 111
113 112 The Qt widget, however, has full syntax highlighting as you type, handled by the
114 `pygments`_ library. The ``--style`` argument exposes access to any style by name that can
115 be found by pygments, and there are several already installed. The ``--colors`` argument,
113 `pygments`_ library. The ``style`` argument exposes access to any style by name that can
114 be found by pygments, and there are several already installed. The ``colors`` argument,
116 115 if unspecified, will be guessed based on the chosen style. Similarly, there are default
117 styles associated with each ``--colors`` option.
116 styles associated with each ``colors`` option.
118 117
119 118
120 Screenshot of ``ipython-qtconsole --colors dark``, which uses the 'monokai' theme by
119 Screenshot of ``ipython-qtconsole colors=linux``, which uses the 'monokai' theme by
121 120 default:
122 121
123 122 .. image:: figs/colors_dark.png
@@ -129,7 +128,7 b' default:'
129 128 on your system.
130 129
131 130 You can also pass the filename of a custom CSS stylesheet, if you want to do your own
132 coloring, via the ``--stylesheet`` argument. The default LightBG stylesheet:
131 coloring, via the ``stylesheet`` argument. The default LightBG stylesheet:
133 132
134 133 .. sourcecode:: css
135 134
@@ -142,6 +141,14 b' coloring, via the ``--stylesheet`` argument. The default LightBG stylesheet:'
142 141 .out-prompt { color: darkred; }
143 142 .out-prompt-number { font-weight: bold; }
144 143
144 Fonts
145 =====
146
147 The QtConsole has configurable via the ConsoleWidget. To change these, set the ``font_family``
148 or ``font_size`` traits of the ConsoleWidget. For instance, to use 9pt Anonymous Pro::
149
150 $> ipython-qtconsole ConsoleWidget.font_family="Anonymous Pro" ConsoleWidget.font_size=9
151
145 152 Process Management
146 153 ==================
147 154
@@ -160,7 +167,7 b' do not have to all be qt frontends - any IPython frontend can connect and run co'
160 167 When you start ipython-qtconsole, there will be an output line, like::
161 168
162 169 To connect another client to this kernel, use:
163 -e --xreq 62109 --sub 62110 --rep 62111 --hb 62112
170 --external shell=62109 iopub=62110 stdin=62111 hb=62112
164 171
165 172 Other frontends can connect to your kernel, and share in the execution. This is great for
166 173 collaboration. The `-e` flag is for 'external'. Starting other consoles with that flag
@@ -169,9 +176,9 b' have to specify each port individually, but for now this copy-paste method is be'
169 176
170 177 By default (for security reasons), the kernel only listens on localhost, so you can only
171 178 connect multiple frontends to the kernel from your local machine. You can specify to
172 listen on an external interface by specifying the ``--ip`` argument::
179 listen on an external interface by specifying the ``ip`` argument::
173 180
174 $> ipython-qtconsole --ip 192.168.1.123
181 $> ipython-qtconsole ip=192.168.1.123
175 182
176 183 If you specify the ip as 0.0.0.0, that refers to all interfaces, so any computer that can
177 184 see yours can connect to the kernel.
@@ -208,10 +215,6 b' Regressions'
208 215 There are some features, where the qt console lags behind the Terminal frontend. We hope
209 216 to have these fixed by 0.11 release.
210 217
211 * Configuration: The Qt frontend and ZMQ kernel are not yet hooked up to the IPython
212 configuration system
213 * History Persistence: Currently the history of a GUI session does
214 not persist between sessions.
215 218 * !cmd input: Due to our use of pexpect, we cannot pass input to subprocesses launched
216 219 using the '!' escape. (this will not be fixed).
217 220
@@ -21,7 +21,7 b' You start IPython with the command::'
21 21
22 22 If invoked with no options, it executes all the files listed in sequence
23 23 and drops you into the interpreter while still acknowledging any options
24 you may have set in your ipythonrc file. This behavior is different from
24 you may have set in your ipython_config.py. This behavior is different from
25 25 standard Python, which when called as python -i will only execute one
26 26 file and ignore your configuration setup.
27 27
@@ -41,9 +41,12 b' Special Threading Options'
41 41
42 42 Previously IPython had command line options for controlling GUI event loop
43 43 integration (-gthread, -qthread, -q4thread, -wthread, -pylab). As of IPython
44 version 0.11, these have been deprecated. Please see the new ``%gui``
44 version 0.11, these have been removed. Please see the new ``%gui``
45 45 magic command or :ref:`this section <gui_support>` for details on the new
46 interface.
46 interface, or specify the gui at the commandline::
47
48 $ ipython gui=qt
49
47 50
48 51 Regular Options
49 52 ---------------
@@ -58,15 +61,15 b' the provided example for more details on what the options do. Options'
58 61 given at the command line override the values set in the ipythonrc file.
59 62
60 63 All options with a [no] prepended can be specified in negated form
61 (-nooption instead of -option) to turn the feature off.
64 (--no-option instead of --option) to turn the feature off.
62 65
63 -help print a help message and exit.
66 -h, --help print a help message and exit.
64 67
65 -pylab
66 Deprecated. See :ref:`Matplotlib support <matplotlib_support>`
68 --pylab, pylab=<name>
69 See :ref:`Matplotlib support <matplotlib_support>`
67 70 for more details.
68 71
69 -autocall <val>
72 autocall=<val>
70 73 Make IPython automatically call any callable object even if you
71 74 didn't type explicit parentheses. For example, 'str 43' becomes
72 75 'str(43)' automatically. The value can be '0' to disable the feature,
@@ -75,25 +78,25 b' All options with a [no] prepended can be specified in negated form'
75 78 objects are automatically called (even if no arguments are
76 79 present). The default is '1'.
77 80
78 -[no]autoindent
81 --[no-]autoindent
79 82 Turn automatic indentation on/off.
80 83
81 -[no]automagic
84 --[no-]automagic
82 85 make magic commands automatic (without needing their first character
83 86 to be %). Type %magic at the IPython prompt for more information.
84 87
85 -[no]autoedit_syntax
88 --[no-]autoedit_syntax
86 89 When a syntax error occurs after editing a file, automatically
87 90 open the file to the trouble causing line for convenient
88 91 fixing.
89 92
90 -[no]banner Print the initial information banner (default on).
93 --[no-]banner Print the initial information banner (default on).
91 94
92 -c <command>
95 c=<command>
93 96 execute the given command string. This is similar to the -c
94 97 option in the normal Python interpreter.
95 98
96 -cache_size, cs <n>
99 cache_size=<n>
97 100 size of the output cache (maximum number of entries to hold in
98 101 memory). The default is 1000, you can change it permanently in your
99 102 config file. Setting it to 0 completely disables the caching system,
@@ -102,15 +105,15 b' All options with a [no] prepended can be specified in negated form'
102 105 because otherwise you'll spend more time re-flushing a too small cache
103 106 than working.
104 107
105 -classic, cl
108 --classic
106 109 Gives IPython a similar feel to the classic Python
107 110 prompt.
108 111
109 -colors <scheme>
112 colors=<scheme>
110 113 Color scheme for prompts and exception reporting. Currently
111 114 implemented: NoColor, Linux and LightBG.
112 115
113 -[no]color_info
116 --[no-]color_info
114 117 IPython can display information about objects via a set of functions,
115 118 and optionally can use colors for this, syntax highlighting source
116 119 code and various other elements. However, because this information is
@@ -124,12 +127,12 b' All options with a [no] prepended can be specified in negated form'
124 127 system. The magic function %color_info allows you to toggle this
125 128 interactively for testing.
126 129
127 -[no]debug
130 --[no-]debug
128 131 Show information about the loading process. Very useful to pin down
129 132 problems with your configuration files or to get details about
130 133 session restores.
131 134
132 -[no]deep_reload:
135 --[no-]deep_reload:
133 136 IPython can use the deep_reload module which reloads changes in
134 137 modules recursively (it replaces the reload() function, so you don't
135 138 need to change anything to use it). deep_reload() forces a full
@@ -141,7 +144,7 b' All options with a [no] prepended can be specified in negated form'
141 144 feature is off by default [which means that you have both
142 145 normal reload() and dreload()].
143 146
144 -editor <name>
147 editor=<name>
145 148 Which editor to use with the %edit command. By default,
146 149 IPython will honor your EDITOR environment variable (if not
147 150 set, vi is the Unix default and notepad the Windows one).
@@ -150,7 +153,7 b' All options with a [no] prepended can be specified in negated form'
150 153 small, lightweight editor here (in case your default EDITOR is
151 154 something like Emacs).
152 155
153 -ipythondir <name>
156 ipython_dir=<name>
154 157 name of your IPython configuration directory IPYTHON_DIR. This
155 158 can also be specified through the environment variable
156 159 IPYTHON_DIR.
@@ -187,28 +190,28 b' All options with a [no] prepended can be specified in negated form'
187 190 our first attempts failed because of inherent limitations of
188 191 Python's Pickle module, so this may have to wait.
189 192
190 -[no]messages
193 --[no-]messages
191 194 Print messages which IPython collects about its startup
192 195 process (default on).
193 196
194 -[no]pdb
197 --[no-]pdb
195 198 Automatically call the pdb debugger after every uncaught
196 199 exception. If you are used to debugging using pdb, this puts
197 200 you automatically inside of it after any call (either in
198 201 IPython or in code called by it) which triggers an exception
199 202 which goes uncaught.
200 203
201 -pydb
204 --pydb
202 205 Makes IPython use the third party "pydb" package as debugger,
203 206 instead of pdb. Requires that pydb is installed.
204 207
205 -[no]pprint
208 --[no-]pprint
206 209 ipython can optionally use the pprint (pretty printer) module
207 210 for displaying results. pprint tends to give a nicer display
208 211 of nested data structures. If you like it, you can turn it on
209 212 permanently in your config file (default off).
210 213
211 -profile, p <name>
214 profile=<name>
212 215
213 216 assume that your config file is ipythonrc-<name> or
214 217 ipy_profile_<name>.py (looks in current dir first, then in
@@ -227,7 +230,7 b' All options with a [no] prepended can be specified in negated form'
227 230 circular file inclusions, IPython will stop if it reaches 15
228 231 recursive inclusions.
229 232
230 -prompt_in1, pi1 <string>
233 pi1=<string>
231 234
232 235 Specify the string used for input prompts. Note that if you are using
233 236 numbered prompts, the number is represented with a '\#' in the
@@ -236,7 +239,7 b' All options with a [no] prepended can be specified in negated form'
236 239 discusses in detail all the available escapes to customize your
237 240 prompts.
238 241
239 -prompt_in2, pi2 <string>
242 pi2=<string>
240 243 Similar to the previous option, but used for the continuation
241 244 prompts. The special sequence '\D' is similar to '\#', but
242 245 with all digits replaced dots (so you can have your
@@ -244,21 +247,22 b' All options with a [no] prepended can be specified in negated form'
244 247 ' .\D.:' (note three spaces at the start for alignment with
245 248 'In [\#]').
246 249
247 -prompt_out,po <string>
250 po=<string>
248 251 String used for output prompts, also uses numbers like
249 252 prompt_in1. Default: 'Out[\#]:'
250 253
251 -quick start in bare bones mode (no config file loaded).
254 --quick
255 start in bare bones mode (no config file loaded).
252 256
253 -rcfile <name>
257 config_file=<name>
254 258 name of your IPython resource configuration file. Normally
255 IPython loads ipythonrc (from current directory) or
256 IPYTHON_DIR/ipythonrc.
259 IPython loads ipython_config.py (from current directory) or
260 IPYTHON_DIR/profile_default.
257 261
258 262 If the loading of your config file fails, IPython starts with
259 263 a bare bones configuration (no modules loaded at all).
260 264
261 -[no]readline
265 --[no-]readline
262 266 use the readline library, which is needed to support name
263 267 completion and command history, among other things. It is
264 268 enabled by default, but may cause problems for users of
@@ -268,7 +272,7 b' All options with a [no] prepended can be specified in negated form'
268 272 IPython's readline and syntax coloring fine, only 'emacs' (M-x
269 273 shell and C-c !) buffers do not.
270 274
271 -screen_length, sl <n>
275 sl=<n>
272 276 number of lines of your screen. This is used to control
273 277 printing of very long strings. Strings longer than this number
274 278 of lines will be sent through a pager instead of directly
@@ -281,41 +285,38 b' All options with a [no] prepended can be specified in negated form'
281 285 reason this isn't working well (it needs curses support), specify
282 286 it yourself. Otherwise don't change the default.
283 287
284 -separate_in, si <string>
288 si=<string>
285 289
286 290 separator before input prompts.
287 291 Default: '\n'
288 292
289 -separate_out, so <string>
293 so=<string>
290 294 separator before output prompts.
291 295 Default: nothing.
292 296
293 -separate_out2, so2
297 so2=<string>
294 298 separator after output prompts.
295 299 Default: nothing.
296 300 For these three options, use the value 0 to specify no separator.
297 301
298 -nosep
302 --nosep
299 303 shorthand for '-SeparateIn 0 -SeparateOut 0 -SeparateOut2
300 304 0'. Simply removes all input/output separators.
301 305
302 -upgrade
303 allows you to upgrade your IPYTHON_DIR configuration when you
306 --init
307 allows you to initialize your IPYTHON_DIR configuration when you
304 308 install a new version of IPython. Since new versions may
305 309 include new command line options or example files, this copies
306 updated ipythonrc-type files. However, it backs up (with a
310 updated config files. However, it backs up (with a
307 311 .old extension) all files which it overwrites so that you can
308 312 merge back any customizations you might have in your personal
309 313 files. Note that you should probably use %upgrade instead,
310 314 it's a safer alternative.
311 315
312 316
313 -Version print version information and exit.
314
315 -wxversion <string>
316 Deprecated.
317 --version print version information and exit.
317 318
318 -xmode <modename>
319 xmode=<modename>
319 320
320 321 Mode for exception reporting.
321 322
@@ -324,7 +324,7 b' made a profile for my project (which is called "parkfield")::'
324 324
325 325 I also added a shell alias for convenience::
326 326
327 alias parkfield="ipython -pylab -profile parkfield"
327 alias parkfield="ipython --pylab profile=parkfield"
328 328
329 329 Now I have a nice little directory with everything I ever type in,
330 330 organized by project and date.
@@ -195,7 +195,7 b' simply start a controller and engines on a single host using the'
195 195 :command:`ipcluster` command. To start a controller and 4 engines on your
196 196 localhost, just do::
197 197
198 $ ipcluster start -n 4
198 $ ipcluster start n=4
199 199
200 200 More details about starting the IPython controller and engines can be found
201 201 :ref:`here <parallel_process>`
@@ -53,11 +53,11 b' these things to happen.'
53 53 Automatic starting using :command:`mpiexec` and :command:`ipcluster`
54 54 --------------------------------------------------------------------
55 55
56 The easiest approach is to use the `mpiexec` mode of :command:`ipcluster`,
56 The easiest approach is to use the `MPIExec` Launchers in :command:`ipcluster`,
57 57 which will first start a controller and then a set of engines using
58 58 :command:`mpiexec`::
59 59
60 $ ipcluster mpiexec -n 4
60 $ ipcluster start n=4 elauncher=MPIExecEngineSetLauncher
61 61
62 62 This approach is best as interrupting :command:`ipcluster` will automatically
63 63 stop and clean up the controller and engines.
@@ -68,14 +68,14 b' Manual starting using :command:`mpiexec`'
68 68 If you want to start the IPython engines using the :command:`mpiexec`, just
69 69 do::
70 70
71 $ mpiexec -n 4 ipengine --mpi=mpi4py
71 $ mpiexec n=4 ipengine mpi=mpi4py
72 72
73 73 This requires that you already have a controller running and that the FURL
74 74 files for the engines are in place. We also have built in support for
75 75 PyTrilinos [PyTrilinos]_, which can be used (assuming is installed) by
76 76 starting the engines with::
77 77
78 $ mpiexec -n 4 ipengine --mpi=pytrilinos
78 $ mpiexec n=4 ipengine mpi=pytrilinos
79 79
80 80 Automatic starting using PBS and :command:`ipcluster`
81 81 ------------------------------------------------------
@@ -110,7 +110,7 b' distributed array. Save the following text in a file called :file:`psum.py`:'
110 110
111 111 Now, start an IPython cluster::
112 112
113 $ ipcluster start -p mpi -n 4
113 $ ipcluster start profile=mpi n=4
114 114
115 115 .. note::
116 116
@@ -19,7 +19,7 b' To follow along with this tutorial, you will need to start the IPython'
19 19 controller and four IPython engines. The simplest way of doing this is to use
20 20 the :command:`ipcluster` command::
21 21
22 $ ipcluster start -n 4
22 $ ipcluster start n=4
23 23
24 24 For more detailed information about starting the controller and engines, see
25 25 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
@@ -57,7 +57,7 b' controller and engines in the following situations:'
57 57
58 58 1. When the controller and engines are all run on localhost. This is useful
59 59 for testing or running on a multicore computer.
60 2. When engines are started using the :command:`mpirun` command that comes
60 2. When engines are started using the :command:`mpiexec` command that comes
61 61 with most MPI [MPI]_ implementations
62 62 3. When engines are started using the PBS [PBS]_ batch system
63 63 (or other `qsub` systems, such as SGE).
@@ -68,7 +68,7 b' controller and engines in the following situations:'
68 68 .. note::
69 69
70 70 Currently :command:`ipcluster` requires that the
71 :file:`~/.ipython/cluster_<profile>/security` directory live on a shared filesystem that is
71 :file:`~/.ipython/profile_<name>/security` directory live on a shared filesystem that is
72 72 seen by both the controller and engines. If you don't have a shared file
73 73 system you will need to use :command:`ipcontroller` and
74 74 :command:`ipengine` directly.
@@ -80,9 +80,9 b' The simplest way to use ipcluster requires no configuration, and will'
80 80 launch a controller and a number of engines on the local machine. For instance,
81 81 to start one controller and 4 engines on localhost, just do::
82 82
83 $ ipcluster start -n 4
83 $ ipcluster start n=4
84 84
85 To see other command line options for the local mode, do::
85 To see other command line options, do::
86 86
87 87 $ ipcluster -h
88 88
@@ -92,12 +92,12 b' Configuring an IPython cluster'
92 92
93 93 Cluster configurations are stored as `profiles`. You can create a new profile with::
94 94
95 $ ipcluster create -p myprofile
95 $ ipython profile create --parallel profile=myprofile
96 96
97 97 This will create the directory :file:`IPYTHONDIR/cluster_myprofile`, and populate it
98 98 with the default configuration files for the three IPython cluster commands. Once
99 99 you edit those files, you can continue to call ipcluster/ipcontroller/ipengine
100 with no arguments beyond ``-p myprofile``, and any configuration will be maintained.
100 with no arguments beyond ``p=myprofile``, and any configuration will be maintained.
101 101
102 102 There is no limit to the number of profiles you can have, so you can maintain a profile for each
103 103 of your common use cases. The default profile will be used whenever the
@@ -112,7 +112,8 b' Using various batch systems with :command:`ipcluster`'
112 112
113 113 :command:`ipcluster` has a notion of Launchers that can start controllers
114 114 and engines with various remote execution schemes. Currently supported
115 models include `mpiexec`, PBS-style (Torque, SGE), and Windows HPC Server.
115 models include :command:`ssh`, :command`mpiexec`, PBS-style (Torque, SGE),
116 and Windows HPC Server.
116 117
117 118 .. note::
118 119
@@ -132,7 +133,7 b' The mpiexec/mpirun mode is useful if you:'
132 133
133 134 If these are satisfied, you can create a new profile::
134 135
135 $ ipcluster create -p mpi
136 $ ipython profile create --parallel profile=mpi
136 137
137 138 and edit the file :file:`IPYTHONDIR/cluster_mpi/ipcluster_config.py`.
138 139
@@ -140,11 +141,11 b' There, instruct ipcluster to use the MPIExec launchers by adding the lines:'
140 141
141 142 .. sourcecode:: python
142 143
143 c.Global.engine_launcher = 'IPython.parallel.apps.launcher.MPIExecEngineSetLauncher'
144 c.IPClusterEnginesApp.engine_launcher = 'IPython.parallel.apps.launcher.MPIExecEngineSetLauncher'
144 145
145 146 If the default MPI configuration is correct, then you can now start your cluster, with::
146 147
147 $ ipcluster start -n 4 -p mpi
148 $ ipcluster start n=4 profile=mpi
148 149
149 150 This does the following:
150 151
@@ -155,7 +156,7 b' If you have a reason to also start the Controller with mpi, you can specify:'
155 156
156 157 .. sourcecode:: python
157 158
158 c.Global.controller_launcher = 'IPython.parallel.apps.launcher.MPIExecControllerLauncher'
159 c.IPClusterStartApp.controller_launcher = 'IPython.parallel.apps.launcher.MPIExecControllerLauncher'
159 160
160 161 .. note::
161 162
@@ -189,7 +190,7 b' The PBS mode uses the Portable Batch System [PBS]_ to start the engines.'
189 190
190 191 As usual, we will start by creating a fresh profile::
191 192
192 $ ipcluster create -p pbs
193 $ ipython profile create --parallel profile=pbs
193 194
194 195 And in :file:`ipcluster_config.py`, we will select the PBS launchers for the controller
195 196 and engines:
@@ -207,35 +208,32 b' to specify your own. Here is a sample PBS script template:'
207 208 #PBS -N ipython
208 209 #PBS -j oe
209 210 #PBS -l walltime=00:10:00
210 #PBS -l nodes=${n/4}:ppn=4
211 #PBS -q $queue
211 #PBS -l nodes={n/4}:ppn=4
212 #PBS -q {queue}
212 213
213 cd $$PBS_O_WORKDIR
214 export PATH=$$HOME/usr/local/bin
215 export PYTHONPATH=$$HOME/usr/local/lib/python2.7/site-packages
216 /usr/local/bin/mpiexec -n ${n} ipengine --cluster_dir=${cluster_dir}
214 cd $PBS_O_WORKDIR
215 export PATH=$HOME/usr/local/bin
216 export PYTHONPATH=$HOME/usr/local/lib/python2.7/site-packages
217 /usr/local/bin/mpiexec -n {n} ipengine profile_dir={profile_dir}
217 218
218 219 There are a few important points about this template:
219 220
220 1. This template will be rendered at runtime using IPython's :mod:`Itpl`
221 template engine.
221 1. This template will be rendered at runtime using IPython's :class:`EvalFormatter`.
222 This is simply a subclass of :class:`string.Formatter` that allows simple expressions
223 on keys.
222 224
223 225 2. Instead of putting in the actual number of engines, use the notation
224 ``${n}`` to indicate the number of engines to be started. You can also uses
225 expressions like ``${n/4}`` in the template to indicate the number of
226 nodes. There will always be a ${n} and ${cluster_dir} variable passed to the template.
226 ``{n}`` to indicate the number of engines to be started. You can also use
227 expressions like ``{n/4}`` in the template to indicate the number of nodes.
228 There will always be ``{n}`` and ``{profile_dir}`` variables passed to the formatter.
227 229 These allow the batch system to know how many engines, and where the configuration
228 files reside. The same is true for the batch queue, with the template variable ``$queue``.
230 files reside. The same is true for the batch queue, with the template variable
231 ``{queue}``.
229 232
230 3. Because ``$`` is a special character used by the template engine, you must
231 escape any ``$`` by using ``$$``. This is important when referring to
232 environment variables in the template, or in SGE, where the config lines start
233 with ``#$``, which will have to be ``#$$``.
234
235 4. Any options to :command:`ipengine` can be given in the batch script
233 3. Any options to :command:`ipengine` can be given in the batch script
236 234 template, or in :file:`ipengine_config.py`.
237 235
238 5. Depending on the configuration of you system, you may have to set
236 4. Depending on the configuration of you system, you may have to set
239 237 environment variables in the script template.
240 238
241 239 The controller template should be similar, but simpler:
@@ -246,12 +244,12 b' The controller template should be similar, but simpler:'
246 244 #PBS -j oe
247 245 #PBS -l walltime=00:10:00
248 246 #PBS -l nodes=1:ppn=4
249 #PBS -q $queue
247 #PBS -q {queue}
250 248
251 cd $$PBS_O_WORKDIR
252 export PATH=$$HOME/usr/local/bin
253 export PYTHONPATH=$$HOME/usr/local/lib/python2.7/site-packages
254 ipcontroller --cluster_dir=${cluster_dir}
249 cd $PBS_O_WORKDIR
250 export PATH=$HOME/usr/local/bin
251 export PYTHONPATH=$HOME/usr/local/lib/python2.7/site-packages
252 ipcontroller profile_dir={profile_dir}
255 253
256 254
257 255 Once you have created these scripts, save them with names like
@@ -267,14 +265,14 b' Once you have created these scripts, save them with names like'
267 265 Alternately, you can just define the templates as strings inside :file:`ipcluster_config`.
268 266
269 267 Whether you are using your own templates or our defaults, the extra configurables available are
270 the number of engines to launch (``$n``, and the batch system queue to which the jobs are to be
271 submitted (``$queue``)). These are configurables, and can be specified in
268 the number of engines to launch (``{n}``, and the batch system queue to which the jobs are to be
269 submitted (``{queue}``)). These are configurables, and can be specified in
272 270 :file:`ipcluster_config`:
273 271
274 272 .. sourcecode:: python
275 273
276 274 c.PBSLauncher.queue = 'veryshort.q'
277 c.PBSEngineSetLauncher.n = 64
275 c.IPClusterEnginesApp.n = 64
278 276
279 277 Note that assuming you are running PBS on a multi-node cluster, the Controller's default behavior
280 278 of listening only on localhost is likely too restrictive. In this case, also assuming the
@@ -287,7 +285,7 b' connections on all its interfaces, by adding in :file:`ipcontroller_config`:'
287 285
288 286 You can now run the cluster with::
289 287
290 $ ipcluster start -p pbs -n 128
288 $ ipcluster start profile=pbs n=128
291 289
292 290 Additional configuration options can be found in the PBS section of :file:`ipcluster_config`.
293 291
@@ -312,7 +310,7 b' nodes and :command:`ipcontroller` can be run remotely as well, or on localhost.'
312 310
313 311 As usual, we start by creating a clean profile::
314 312
315 $ ipcluster create -p ssh
313 $ ipython profile create --parallel profile=ssh
316 314
317 315 To use this mode, select the SSH launchers in :file:`ipcluster_config.py`:
318 316
@@ -334,8 +332,8 b" The controller's remote location and configuration can be specified:"
334 332 # Set the arguments to be passed to ipcontroller
335 333 # note that remotely launched ipcontroller will not get the contents of
336 334 # the local ipcontroller_config.py unless it resides on the *remote host*
337 # in the location specified by the --cluster_dir argument.
338 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
335 # in the location specified by the `profile_dir` argument.
336 # c.SSHControllerLauncher.program_args = ['--reuse', 'ip=0.0.0.0', 'profile_dir=/path/to/cd']
339 337
340 338 .. note::
341 339
@@ -351,7 +349,7 b' on that host.'
351 349
352 350 c.SSHEngineSetLauncher.engines = { 'host1.example.com' : 2,
353 351 'host2.example.com' : 5,
354 'host3.example.com' : (1, ['--cluster_dir', '/home/different/location']),
352 'host3.example.com' : (1, ['profile_dir=/home/different/location']),
355 353 'host4.example.com' : 8 }
356 354
357 355 * The `engines` dict, where the keys are the host we want to run engines on and
@@ -364,7 +362,7 b' a single location:'
364 362
365 363 .. sourcecode:: python
366 364
367 c.SSHEngineSetLauncher.engine_args = ['--cluster_dir', '/path/to/cluster_ssh']
365 c.SSHEngineSetLauncher.engine_args = ['profile_dir=/path/to/cluster_ssh']
368 366
369 367 Current limitations of the SSH mode of :command:`ipcluster` are:
370 368
@@ -419,7 +417,7 b' When the controller and engines are running on different hosts, things are'
419 417 slightly more complicated, but the underlying ideas are the same:
420 418
421 419 1. Start the controller on a host using :command:`ipcontroller`.
422 2. Copy :file:`ipcontroller-engine.json` from :file:`~/.ipython/cluster_<profile>/security` on
420 2. Copy :file:`ipcontroller-engine.json` from :file:`~/.ipython/profile_<name>/security` on
423 421 the controller's host to the host where the engines will run.
424 422 3. Use :command:`ipengine` on the engine's hosts to start the engines.
425 423
@@ -427,7 +425,7 b' The only thing you have to be careful of is to tell :command:`ipengine` where'
427 425 the :file:`ipcontroller-engine.json` file is located. There are two ways you
428 426 can do this:
429 427
430 * Put :file:`ipcontroller-engine.json` in the :file:`~/.ipython/cluster_<profile>/security`
428 * Put :file:`ipcontroller-engine.json` in the :file:`~/.ipython/profile_<name>/security`
431 429 directory on the engine's host, where it will be found automatically.
432 430 * Call :command:`ipengine` with the ``--file=full_path_to_the_file``
433 431 flag.
@@ -439,7 +437,7 b' The ``--file`` flag works like this::'
439 437 .. note::
440 438
441 439 If the controller's and engine's hosts all have a shared file system
442 (:file:`~/.ipython/cluster_<profile>/security` is the same on all of them), then things
440 (:file:`~/.ipython/profile_<name>/security` is the same on all of them), then things
443 441 will just work!
444 442
445 443 Make JSON files persistent
@@ -452,10 +450,10 b' you want to unlock the door and enter your house. As with your house, you want'
452 450 to be able to create the key (or JSON file) once, and then simply use it at
453 451 any point in the future.
454 452
455 To do this, the only thing you have to do is specify the `-r` flag, so that
453 To do this, the only thing you have to do is specify the `--reuse` flag, so that
456 454 the connection information in the JSON files remains accurate::
457 455
458 $ ipcontroller -r
456 $ ipcontroller --reuse
459 457
460 458 Then, just copy the JSON files over the first time and you are set. You can
461 459 start and stop the controller and engines any many times as you want in the
@@ -474,7 +472,7 b' Log files'
474 472
475 473 All of the components of IPython have log files associated with them.
476 474 These log files can be extremely useful in debugging problems with
477 IPython and can be found in the directory :file:`~/.ipython/cluster_<profile>/log`.
475 IPython and can be found in the directory :file:`~/.ipython/profile_<name>/log`.
478 476 Sending the log files to us will often help us to debug any problems.
479 477
480 478
@@ -15,8 +15,8 b" computing. This feature brings up the important question of IPython's security"
15 15 model. This document gives details about this model and how it is implemented
16 16 in IPython's architecture.
17 17
18 Processs and network topology
19 =============================
18 Process and network topology
19 ============================
20 20
21 21 To enable parallel computing, IPython has a number of different processes that
22 22 run. These processes are discussed at length in the IPython documentation and
@@ -36,15 +36,9 b' are summarized here:'
36 36 interactive Python process that is used to coordinate the
37 37 engines to get a parallel computation done.
38 38
39 Collectively, these processes are called the IPython *kernel*, and the hub and schedulers
39 Collectively, these processes are called the IPython *cluster*, and the hub and schedulers
40 40 together are referred to as the *controller*.
41 41
42 .. note::
43
44 Are these really still referred to as the Kernel? It doesn't seem so to me. 'cluster'
45 seems more accurate.
46
47 -MinRK
48 42
49 43 These processes communicate over any transport supported by ZeroMQ (tcp,pgm,infiniband,ipc)
50 44 with a well defined topology. The IPython hub and schedulers listen on sockets. Upon
@@ -118,20 +112,23 b' controller were on loopback on the connecting machine.'
118 112 Authentication
119 113 --------------
120 114
121 To protect users of shared machines, an execution key is used to authenticate all messages.
115 To protect users of shared machines, [HMAC]_ digests are used to sign messages, using a
116 shared key.
122 117
123 118 The Session object that handles the message protocol uses a unique key to verify valid
124 119 messages. This can be any value specified by the user, but the default behavior is a
125 pseudo-random 128-bit number, as generated by `uuid.uuid4()`. This key is checked on every
126 message everywhere it is unpacked (Controller, Engine, and Client) to ensure that it came
127 from an authentic user, and no messages that do not contain this key are acted upon in any
128 way.
129
130 There is exactly one key per cluster - it must be the same everywhere. Typically, the
131 controller creates this key, and stores it in the private connection files
120 pseudo-random 128-bit number, as generated by `uuid.uuid4()`. This key is used to
121 initialize an HMAC object, which digests all messages, and includes that digest as a
122 signature and part of the message. Every message that is unpacked (on Controller, Engine,
123 and Client) will also be digested by the receiver, ensuring that the sender's key is the
124 same as the receiver's. No messages that do not contain this key are acted upon in any
125 way. The key itself is never sent over the network.
126
127 There is exactly one shared key per cluster - it must be the same everywhere. Typically,
128 the controller creates this key, and stores it in the private connection files
132 129 `ipython-{engine|client}.json`. These files are typically stored in the
133 `~/.ipython/cluster_<profile>/security` directory, and are maintained as readable only by
134 the owner, just as is common practice with a user's keys in their `.ssh` directory.
130 `~/.ipython/profile_<name>/security` directory, and are maintained as readable only by the
131 owner, just as is common practice with a user's keys in their `.ssh` directory.
135 132
136 133 .. warning::
137 134
@@ -171,13 +168,15 b' It is highly unlikely that an execution key could be guessed by an attacker'
171 168 in a brute force guessing attack. A given instance of the IPython controller
172 169 only runs for a relatively short amount of time (on the order of hours). Thus
173 170 an attacker would have only a limited amount of time to test a search space of
174 size 2**128.
171 size 2**128. For added security, users can have arbitrarily long keys.
175 172
176 173 .. warning::
177 174
178 If the attacker has gained enough access to intercept loopback connections on
179 *either* the controller or client, then the key is easily deduced from network
180 traffic.
175 If the attacker has gained enough access to intercept loopback connections on *either* the
176 controller or client, then a duplicate message can be sent. To protect against this,
177 recipients only allow each signature once, and consider duplicates invalid. However,
178 the duplicate message could be sent to *another* recipient using the same key,
179 and it would be considered valid.
181 180
182 181
183 182 Unauthorized engines
@@ -322,3 +321,4 b' channel is established.'
322 321
323 322 .. [OpenSSH] <http://www.openssh.com/>
324 323 .. [Paramiko] <http://www.lag.net/paramiko/>
324 .. [HMAC] <http://tools.ietf.org/html/rfc2104.html>
@@ -24,7 +24,7 b' To follow along with this tutorial, you will need to start the IPython'
24 24 controller and four IPython engines. The simplest way of doing this is to use
25 25 the :command:`ipcluster` command::
26 26
27 $ ipcluster start -n 4
27 $ ipcluster start n=4
28 28
29 29 For more detailed information about starting the controller and engines, see
30 30 our :ref:`introduction <ip1par>` to using IPython for parallel computing.
@@ -342,17 +342,17 b' Schedulers'
342 342
343 343 There are a variety of valid ways to determine where jobs should be assigned in a
344 344 load-balancing situation. In IPython, we support several standard schemes, and
345 even make it easy to define your own. The scheme can be selected via the ``--scheme``
346 argument to :command:`ipcontroller`, or in the :attr:`HubFactory.scheme` attribute
345 even make it easy to define your own. The scheme can be selected via the ``scheme``
346 argument to :command:`ipcontroller`, or in the :attr:`TaskScheduler.schemename` attribute
347 347 of a controller config object.
348 348
349 349 The built-in routing schemes:
350 350
351 351 To select one of these schemes, simply do::
352 352
353 $ ipcontroller --scheme <schemename>
353 $ ipcontroller scheme=<schemename>
354 354 for instance:
355 $ ipcontroller --scheme lru
355 $ ipcontroller scheme=lru
356 356
357 357 lru: Least Recently Used
358 358
@@ -162,7 +162,7 b' cluster using the Windows HPC Server 2008 job scheduler. To make sure that'
162 162 to start an IPython cluster on your local host. To do this, open a Windows
163 163 Command Prompt and type the following command::
164 164
165 ipcluster start -n 2
165 ipcluster start n=2
166 166
167 167 You should see a number of messages printed to the screen, ending with
168 168 "IPython cluster: started". The result should look something like the following
@@ -179,11 +179,11 b' describe how to configure and run an IPython cluster on an actual compute'
179 179 cluster running Windows HPC Server 2008. Here is an outline of the needed
180 180 steps:
181 181
182 1. Create a cluster profile using: ``ipcluster create -p mycluster``
182 1. Create a cluster profile using: ``ipython profile create --parallel profile=mycluster``
183 183
184 184 2. Edit configuration files in the directory :file:`.ipython\\cluster_mycluster`
185 185
186 3. Start the cluster using: ``ipcluser start -p mycluster -n 32``
186 3. Start the cluster using: ``ipcluser start profile=mycluster n=32``
187 187
188 188 Creating a cluster profile
189 189 --------------------------
@@ -198,13 +198,13 b' directory is a specially named directory (typically located in the'
198 198 :file:`.ipython` subdirectory of your home directory) that contains the
199 199 configuration files for a particular cluster profile, as well as log files and
200 200 security keys. The naming convention for cluster directories is:
201 :file:`cluster_<profile name>`. Thus, the cluster directory for a profile named
201 :file:`profile_<profile name>`. Thus, the cluster directory for a profile named
202 202 "foo" would be :file:`.ipython\\cluster_foo`.
203 203
204 204 To create a new cluster profile (named "mycluster") and the associated cluster
205 205 directory, type the following command at the Windows Command Prompt::
206 206
207 ipcluster create -p mycluster
207 ipython profile create --parallel profile=mycluster
208 208
209 209 The output of this command is shown in the screenshot below. Notice how
210 210 :command:`ipcluster` prints out the location of the newly created cluster
@@ -257,7 +257,7 b' Starting the cluster profile'
257 257 Once a cluster profile has been configured, starting an IPython cluster using
258 258 the profile is simple::
259 259
260 ipcluster start -p mycluster -n 32
260 ipcluster start profile=mycluster n=32
261 261
262 262 The ``-n`` option tells :command:`ipcluster` how many engines to start (in
263 263 this case 32). Stopping the cluster is as simple as typing Control-C.
@@ -213,7 +213,7 b" if 'setuptools' in sys.modules:"
213 213 setuptools_extra_args['entry_points'] = find_scripts(True)
214 214 setup_args['extras_require'] = dict(
215 215 parallel = 'pyzmq>=2.1.4',
216 zmq = 'pyzmq>=2.0.10.1',
216 zmq = 'pyzmq>=2.1.4',
217 217 doc='Sphinx>=0.3',
218 218 test='nose>=0.10.1',
219 219 )
@@ -103,7 +103,7 b' def find_packages():'
103 103 Find all of IPython's packages.
104 104 """
105 105 packages = ['IPython']
106 add_package(packages, 'config', tests=True, others=['default','profile'])
106 add_package(packages, 'config', tests=True, others=['profile'])
107 107 add_package(packages, 'core', tests=True)
108 108 add_package(packages, 'deathrow', tests=True)
109 109 add_package(packages, 'extensions')
@@ -150,8 +150,8 b' def find_package_data():'
150 150 # This is not enough for these things to appear in an sdist.
151 151 # We need to muck with the MANIFEST to get this to work
152 152 package_data = {
153 'IPython.config.userconfig' : ['*'],
154 'IPython.testing' : ['*.txt']
153 'IPython.config.profile' : ['README', '*/*.py'],
154 'IPython.testing' : ['*.txt'],
155 155 }
156 156 return package_data
157 157
@@ -280,7 +280,7 b' def find_scripts(entry_points=False):'
280 280 'irunner = IPython.lib.irunner:main'
281 281 ]
282 282 gui_scripts = [
283 'ipython-qtconsole = IPython.frontend.qt.console.ipythonqt:main',
283 'ipython-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
284 284 ]
285 285 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
286 286 else:
@@ -292,7 +292,6 b' def find_scripts(entry_points=False):'
292 292 pjoin(parallel_scripts, 'ipcluster'),
293 293 pjoin(parallel_scripts, 'iplogger'),
294 294 pjoin(main_scripts, 'ipython'),
295 pjoin(main_scripts, 'ipython-qtconsole'),
296 295 pjoin(main_scripts, 'pycolor'),
297 296 pjoin(main_scripts, 'irunner'),
298 297 pjoin(main_scripts, 'iptest')
@@ -139,8 +139,9 b' def check_for_pyzmq():'
139 139 print_status('pyzmq', "no (required for qtconsole and parallel computing capabilities)")
140 140 return False
141 141 else:
142 if zmq.__version__ < '2.0.10':
143 print_status('pyzmq', "no (require >= 2.0.10 for qtconsole and parallel computing capabilities)")
142 if zmq.__version__ < '2.1.4':
143 print_status('pyzmq', "no (have %s, but require >= 2.1.4 for"
144 " qtconsole and parallel computing capabilities)"%zmq.__version__)
144 145
145 146 else:
146 147 print_status("pyzmq", zmq.__version__)
1 NO CONTENT: file was removed
@@ -1,241 +0,0 b''
1 import os
2
3 c = get_config()
4
5 #-----------------------------------------------------------------------------
6 # Select which launchers to use
7 #-----------------------------------------------------------------------------
8
9 # This allows you to control what method is used to start the controller
10 # and engines. The following methods are currently supported:
11 # - Start as a regular process on localhost.
12 # - Start using mpiexec.
13 # - Start using the Windows HPC Server 2008 scheduler
14 # - Start using PBS/SGE
15 # - Start using SSH
16
17
18 # The selected launchers can be configured below.
19
20 # Options are:
21 # - LocalControllerLauncher
22 # - MPIExecControllerLauncher
23 # - PBSControllerLauncher
24 # - SGEControllerLauncher
25 # - WindowsHPCControllerLauncher
26 # c.Global.controller_launcher = 'IPython.parallel.apps.launcher.LocalControllerLauncher'
27 # c.Global.controller_launcher = 'IPython.parallel.apps.launcher.PBSControllerLauncher'
28
29 # Options are:
30 # - LocalEngineSetLauncher
31 # - MPIExecEngineSetLauncher
32 # - PBSEngineSetLauncher
33 # - SGEEngineSetLauncher
34 # - WindowsHPCEngineSetLauncher
35 # c.Global.engine_launcher = 'IPython.parallel.apps.launcher.LocalEngineSetLauncher'
36
37 #-----------------------------------------------------------------------------
38 # Global configuration
39 #-----------------------------------------------------------------------------
40
41 # The default number of engines that will be started. This is overridden by
42 # the -n command line option: "ipcluster start -n 4"
43 # c.Global.n = 2
44
45 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
46 # c.Global.log_to_file = False
47
48 # Remove old logs from cluster_dir/log before starting.
49 # c.Global.clean_logs = True
50
51 # The working directory for the process. The application will use os.chdir
52 # to change to this directory before starting.
53 # c.Global.work_dir = os.getcwd()
54
55
56 #-----------------------------------------------------------------------------
57 # Local process launchers
58 #-----------------------------------------------------------------------------
59
60 # The command line arguments to call the controller with.
61 # c.LocalControllerLauncher.controller_args = \
62 # ['--log-to-file','--log-level', '40']
63
64 # The working directory for the controller
65 # c.LocalEngineSetLauncher.work_dir = u''
66
67 # Command line argument passed to the engines.
68 # c.LocalEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
69
70 #-----------------------------------------------------------------------------
71 # MPIExec launchers
72 #-----------------------------------------------------------------------------
73
74 # The mpiexec/mpirun command to use in both the controller and engines.
75 # c.MPIExecLauncher.mpi_cmd = ['mpiexec']
76
77 # Additional arguments to pass to the actual mpiexec command.
78 # c.MPIExecLauncher.mpi_args = []
79
80 # The mpiexec/mpirun command and args can be overridden if they should be different
81 # for controller and engines.
82 # c.MPIExecControllerLauncher.mpi_cmd = ['mpiexec']
83 # c.MPIExecControllerLauncher.mpi_args = []
84 # c.MPIExecEngineSetLauncher.mpi_cmd = ['mpiexec']
85 # c.MPIExecEngineSetLauncher.mpi_args = []
86
87 # The command line argument to call the controller with.
88 # c.MPIExecControllerLauncher.controller_args = \
89 # ['--log-to-file','--log-level', '40']
90
91 # Command line argument passed to the engines.
92 # c.MPIExecEngineSetLauncher.engine_args = ['--log-to-file','--log-level', '40']
93
94 # The default number of engines to start if not given elsewhere.
95 # c.MPIExecEngineSetLauncher.n = 1
96
97 #-----------------------------------------------------------------------------
98 # SSH launchers
99 #-----------------------------------------------------------------------------
100
101 # ipclusterz can be used to launch controller and engines remotely via ssh.
102 # Note that currently ipclusterz does not do any file distribution, so if
103 # machines are not on a shared filesystem, config and json files must be
104 # distributed. For this reason, the reuse_files defaults to True on an
105 # ssh-launched Controller. This flag can be overridded by the program_args
106 # attribute of c.SSHControllerLauncher.
107
108 # set the ssh cmd for launching remote commands. The default is ['ssh']
109 # c.SSHLauncher.ssh_cmd = ['ssh']
110
111 # set the ssh cmd for launching remote commands. The default is ['ssh']
112 # c.SSHLauncher.ssh_args = ['tt']
113
114 # Set the user and hostname for the controller
115 # c.SSHControllerLauncher.hostname = 'controller.example.com'
116 # c.SSHControllerLauncher.user = os.environ.get('USER','username')
117
118 # Set the arguments to be passed to ipcontrollerz
119 # note that remotely launched ipcontrollerz will not get the contents of
120 # the local ipcontrollerz_config.py unless it resides on the *remote host*
121 # in the location specified by the --cluster_dir argument.
122 # c.SSHControllerLauncher.program_args = ['-r', '-ip', '0.0.0.0', '--cluster_dir', '/path/to/cd']
123
124 # Set the default args passed to ipenginez for SSH launched engines
125 # c.SSHEngineSetLauncher.engine_args = ['--mpi', 'mpi4py']
126
127 # SSH engines are launched as a dict of locations/n-engines.
128 # if a value is a tuple instead of an int, it is assumed to be of the form
129 # (n, [args]), setting the arguments to passed to ipenginez on `host`.
130 # otherwise, c.SSHEngineSetLauncher.engine_args will be used as the default.
131
132 # In this case, there will be 3 engines at my.example.com, and
133 # 2 at you@ipython.scipy.org with a special json connector location.
134 # c.SSHEngineSetLauncher.engines = {'my.example.com' : 3,
135 # 'you@ipython.scipy.org' : (2, ['-f', '/path/to/ipcontroller-engine.json']}
136 # }
137
138 #-----------------------------------------------------------------------------
139 # Unix batch (PBS) schedulers launchers
140 #-----------------------------------------------------------------------------
141
142 # SGE and PBS are very similar. All configurables in this section called 'PBS*'
143 # also exist as 'SGE*'.
144
145 # The command line program to use to submit a PBS job.
146 # c.PBSLauncher.submit_command = ['qsub']
147
148 # The command line program to use to delete a PBS job.
149 # c.PBSLauncher.delete_command = ['qdel']
150
151 # The PBS queue in which the job should run
152 # c.PBSLauncher.queue = 'myqueue'
153
154 # A regular expression that takes the output of qsub and find the job id.
155 # c.PBSLauncher.job_id_regexp = r'\d+'
156
157 # If for some reason the Controller and Engines have different options above, they
158 # can be set as c.PBSControllerLauncher.<option> etc.
159
160 # PBS and SGE have default templates, but you can specify your own, either as strings
161 # or from files, as described here:
162
163 # The batch submission script used to start the controller. This is where
164 # environment variables would be setup, etc. This string is interpreted using
165 # the Itpl module in IPython.external. Basically, you can use ${n} for the
166 # number of engine and ${cluster_dir} for the cluster_dir.
167 # c.PBSControllerLauncher.batch_template = """
168 # #PBS -N ipcontroller
169 # #PBS -q $queue
170 #
171 # ipcontrollerz --cluster-dir $cluster_dir
172 # """
173
174 # You can also load this template from a file
175 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
176
177 # The name of the instantiated batch script that will actually be used to
178 # submit the job. This will be written to the cluster directory.
179 # c.PBSControllerLauncher.batch_file_name = u'pbs_controller'
180
181 # The batch submission script used to start the engines. This is where
182 # environment variables would be setup, etc. This string is interpreted using
183 # the Itpl module in IPython.external. Basically, you can use ${n} for the
184 # number of engine and ${cluster_dir} for the cluster_dir.
185 # c.PBSEngineSetLauncher.batch_template = """
186 # #PBS -N ipcontroller
187 # #PBS -l nprocs=$n
188 #
189 # ipenginez --cluster-dir $cluster_dir$s
190 # """
191
192 # You can also load this template from a file
193 # c.PBSControllerLauncher.batch_template_file = u"/path/to/my/template.sh"
194
195 # The name of the instantiated batch script that will actually be used to
196 # submit the job. This will be written to the cluster directory.
197 # c.PBSEngineSetLauncher.batch_file_name = u'pbs_engines'
198
199
200
201 #-----------------------------------------------------------------------------
202 # Windows HPC Server 2008 launcher configuration
203 #-----------------------------------------------------------------------------
204
205 # c.IPControllerJob.job_name = 'IPController'
206 # c.IPControllerJob.is_exclusive = False
207 # c.IPControllerJob.username = r'USERDOMAIN\USERNAME'
208 # c.IPControllerJob.priority = 'Highest'
209 # c.IPControllerJob.requested_nodes = ''
210 # c.IPControllerJob.project = 'MyProject'
211
212 # c.IPControllerTask.task_name = 'IPController'
213 # c.IPControllerTask.controller_cmd = [u'ipcontroller.exe']
214 # c.IPControllerTask.controller_args = ['--log-to-file', '--log-level', '40']
215 # c.IPControllerTask.environment_variables = {}
216
217 # c.WindowsHPCControllerLauncher.scheduler = 'HEADNODE'
218 # c.WindowsHPCControllerLauncher.job_file_name = u'ipcontroller_job.xml'
219
220
221 # c.IPEngineSetJob.job_name = 'IPEngineSet'
222 # c.IPEngineSetJob.is_exclusive = False
223 # c.IPEngineSetJob.username = r'USERDOMAIN\USERNAME'
224 # c.IPEngineSetJob.priority = 'Highest'
225 # c.IPEngineSetJob.requested_nodes = ''
226 # c.IPEngineSetJob.project = 'MyProject'
227
228 # c.IPEngineTask.task_name = 'IPEngine'
229 # c.IPEngineTask.engine_cmd = [u'ipengine.exe']
230 # c.IPEngineTask.engine_args = ['--log-to-file', '--log-level', '40']
231 # c.IPEngineTask.environment_variables = {}
232
233 # c.WindowsHPCEngineSetLauncher.scheduler = 'HEADNODE'
234 # c.WindowsHPCEngineSetLauncher.job_file_name = u'ipengineset_job.xml'
235
236
237
238
239
240
241
@@ -1,180 +0,0 b''
1 from IPython.config.loader import Config
2
3 c = get_config()
4
5 #-----------------------------------------------------------------------------
6 # Global configuration
7 #-----------------------------------------------------------------------------
8
9 # Basic Global config attributes
10
11 # Start up messages are logged to stdout using the logging module.
12 # These all happen before the twisted reactor is started and are
13 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
14 # and smaller is more verbose.
15 # c.Global.log_level = 20
16
17 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
18 # c.Global.log_to_file = False
19
20 # Remove old logs from cluster_dir/log before starting.
21 # c.Global.clean_logs = True
22
23 # A list of Python statements that will be run before starting the
24 # controller. This is provided because occasionally certain things need to
25 # be imported in the controller for pickling to work.
26 # c.Global.import_statements = ['import math']
27
28 # Reuse the controller's JSON files. If False, JSON files are regenerated
29 # each time the controller is run. If True, they will be reused, *but*, you
30 # also must set the network ports by hand. If set, this will override the
31 # values set for the client and engine connections below.
32 # c.Global.reuse_files = True
33
34 # Enable exec_key authentication on all messages. Default is True
35 # c.Global.secure = True
36
37 # The working directory for the process. The application will use os.chdir
38 # to change to this directory before starting.
39 # c.Global.work_dir = os.getcwd()
40
41 # The log url for logging to an `iploggerz` application. This will override
42 # log-to-file.
43 # c.Global.log_url = 'tcp://127.0.0.1:20202'
44
45 # The specific external IP that is used to disambiguate multi-interface URLs.
46 # The default behavior is to guess from external IPs gleaned from `socket`.
47 # c.Global.location = '192.168.1.123'
48
49 # The ssh server remote clients should use to connect to this controller.
50 # It must be a machine that can see the interface specified in client_ip.
51 # The default for client_ip is localhost, in which case the sshserver must
52 # be an external IP of the controller machine.
53 # c.Global.sshserver = 'controller.example.com'
54
55 # the url to use for registration. If set, this overrides engine-ip,
56 # engine-transport client-ip,client-transport, and regport.
57 # c.RegistrationFactory.url = 'tcp://*:12345'
58
59 # the port to use for registration. Clients and Engines both use this
60 # port for registration.
61 # c.RegistrationFactory.regport = 10101
62
63 #-----------------------------------------------------------------------------
64 # Configure the Task Scheduler
65 #-----------------------------------------------------------------------------
66
67 # The routing scheme. 'pure' will use the pure-ZMQ scheduler. Any other
68 # value will use a Python scheduler with various routing schemes.
69 # python schemes are: lru, weighted, random, twobin. Default is 'weighted'.
70 # Note that the pure ZMQ scheduler does not support many features, such as
71 # dying engines, dependencies, or engine-subset load-balancing.
72 # c.ControllerFactory.scheme = 'pure'
73
74 # The Python scheduler can limit the number of outstanding tasks per engine
75 # by using an HWM option. This allows engines with long-running tasks
76 # to not steal too many tasks from other engines. The default is 0, which
77 # means agressively distribute messages, never waiting for them to finish.
78 # c.TaskScheduler.hwm = 0
79
80 # Whether to use Threads or Processes to start the Schedulers. Threads will
81 # use less resources, but potentially reduce throughput. Default is to
82 # use processes. Note that the a Python scheduler will always be in a Process.
83 # c.ControllerFactory.usethreads
84
85 #-----------------------------------------------------------------------------
86 # Configure the Hub
87 #-----------------------------------------------------------------------------
88
89 # Which class to use for the db backend. Currently supported are DictDB (the
90 # default), and MongoDB. Uncomment this line to enable MongoDB, which will
91 # slow-down the Hub's responsiveness, but also reduce its memory footprint.
92 # c.HubFactory.db_class = 'IPython.parallel.controller.mongodb.MongoDB'
93
94 # The heartbeat ping frequency. This is the frequency (in ms) at which the
95 # Hub pings engines for heartbeats. This determines how quickly the Hub
96 # will react to engines coming and going. A lower number means faster response
97 # time, but more network activity. The default is 100ms
98 # c.HubFactory.ping = 100
99
100 # HubFactory queue port pairs, to set by name: mux, iopub, control, task. Set
101 # each as a tuple of length 2 of ints. The default is to find random
102 # available ports
103 # c.HubFactory.mux = (10102,10112)
104
105 #-----------------------------------------------------------------------------
106 # Configure the client connections
107 #-----------------------------------------------------------------------------
108
109 # Basic client connection config attributes
110
111 # The network interface the controller will listen on for client connections.
112 # This should be an IP address or interface on the controller. An asterisk
113 # means listen on all interfaces. The transport can be any transport
114 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
115 # c.HubFactory.client_ip = '*'
116 # c.HubFactory.client_transport = 'tcp'
117
118 # individual client ports to configure by name: query_port, notifier_port
119 # c.HubFactory.query_port = 12345
120
121 #-----------------------------------------------------------------------------
122 # Configure the engine connections
123 #-----------------------------------------------------------------------------
124
125 # Basic config attributes for the engine connections.
126
127 # The network interface the controller will listen on for engine connections.
128 # This should be an IP address or interface on the controller. An asterisk
129 # means listen on all interfaces. The transport can be any transport
130 # supported by zeromq (tcp,epgm,pgm,ib,ipc):
131 # c.HubFactory.engine_ip = '*'
132 # c.HubFactory.engine_transport = 'tcp'
133
134 # set the engine heartbeat ports to use:
135 # c.HubFactory.hb = (10303,10313)
136
137 #-----------------------------------------------------------------------------
138 # Configure the TaskRecord database backend
139 #-----------------------------------------------------------------------------
140
141 # For memory/persistance reasons, tasks can be stored out-of-memory in a database.
142 # Currently, only sqlite and mongodb are supported as backends, but the interface
143 # is fairly simple, so advanced developers could write their own backend.
144
145 # ----- in-memory configuration --------
146 # this line restores the default behavior: in-memory storage of all results.
147 # c.HubFactory.db_class = 'IPython.parallel.controller.dictdb.DictDB'
148
149 # ----- sqlite configuration --------
150 # use this line to activate sqlite:
151 # c.HubFactory.db_class = 'IPython.parallel.controller.sqlitedb.SQLiteDB'
152
153 # You can specify the name of the db-file. By default, this will be located
154 # in the active cluster_dir, e.g. ~/.ipython/clusterz_default/tasks.db
155 # c.SQLiteDB.filename = 'tasks.db'
156
157 # You can also specify the location of the db-file, if you want it to be somewhere
158 # other than the cluster_dir.
159 # c.SQLiteDB.location = '/scratch/'
160
161 # This will specify the name of the table for the controller to use. The default
162 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
163 # this will result in results persisting for multiple sessions.
164 # c.SQLiteDB.table = 'results'
165
166 # ----- mongodb configuration --------
167 # use this line to activate mongodb:
168 # c.HubFactory.db_class = 'IPython.parallel.controller.mongodb.MongoDB'
169
170 # You can specify the args and kwargs pymongo will use when creating the Connection.
171 # For more information on what these options might be, see pymongo documentation.
172 # c.MongoDB.connection_kwargs = {}
173 # c.MongoDB.connection_args = []
174
175 # This will specify the name of the mongo database for the controller to use. The default
176 # behavior is to use the session ID of the SessionFactory object (a uuid). Overriding
177 # this will result in task results persisting through multiple sessions.
178 # c.MongoDB.database = 'ipythondb'
179
180
@@ -1,85 +0,0 b''
1 c = get_config()
2
3 #-----------------------------------------------------------------------------
4 # Global configuration
5 #-----------------------------------------------------------------------------
6
7 # Start up messages are logged to stdout using the logging module.
8 # These all happen before the twisted reactor is started and are
9 # useful for debugging purposes. Can be (10=DEBUG,20=INFO,30=WARN,40=CRITICAL)
10 # and smaller is more verbose.
11 # c.Global.log_level = 20
12
13 # Log to a file in cluster_dir/log, otherwise just log to sys.stdout.
14 # c.Global.log_to_file = False
15
16 # Remove old logs from cluster_dir/log before starting.
17 # c.Global.clean_logs = True
18
19 # A list of strings that will be executed in the users namespace on the engine
20 # before it connects to the controller.
21 # c.Global.exec_lines = ['import numpy']
22
23 # The engine will try to connect to the controller multiple times, to allow
24 # the controller time to startup and write its FURL file. These parameters
25 # control the number of retries (connect_max_tries) and the initial delay
26 # (connect_delay) between attemps. The actual delay between attempts gets
27 # longer each time by a factor of 1.5 (delay[i] = 1.5*delay[i-1])
28 # those attemps.
29 # c.Global.connect_delay = 0.1
30 # c.Global.connect_max_tries = 15
31
32 # By default, the engine will look for the controller's JSON file in its own
33 # cluster directory. Sometimes, the JSON file will be elsewhere and this
34 # attribute can be set to the full path of the JSON file.
35 # c.Global.url_file = u'/path/to/my/ipcontroller-engine.json'
36
37 # The working directory for the process. The application will use os.chdir
38 # to change to this directory before starting.
39 # c.Global.work_dir = os.getcwd()
40
41 #-----------------------------------------------------------------------------
42 # MPI configuration
43 #-----------------------------------------------------------------------------
44
45 # Upon starting the engine can be configured to call MPI_Init. This section
46 # configures that.
47
48 # Select which MPI section to execute to setup MPI. The value of this
49 # attribute must match the name of another attribute in the MPI config
50 # section (mpi4py, pytrilinos, etc.). This can also be set by the --mpi
51 # command line option.
52 # c.MPI.use = ''
53
54 # Initialize MPI using mpi4py. To use this, set c.MPI.use = 'mpi4py' to use
55 # --mpi=mpi4py at the command line.
56 # c.MPI.mpi4py = """from mpi4py import MPI as mpi
57 # mpi.size = mpi.COMM_WORLD.Get_size()
58 # mpi.rank = mpi.COMM_WORLD.Get_rank()
59 # """
60
61 # Initialize MPI using pytrilinos. To use this, set c.MPI.use = 'pytrilinos'
62 # to use --mpi=pytrilinos at the command line.
63 # c.MPI.pytrilinos = """from PyTrilinos import Epetra
64 # class SimpleStruct:
65 # pass
66 # mpi = SimpleStruct()
67 # mpi.rank = 0
68 # mpi.size = 0
69 # """
70
71 #-----------------------------------------------------------------------------
72 # Developer level configuration attributes
73 #-----------------------------------------------------------------------------
74
75 # You shouldn't have to modify anything in this section. These attributes
76 # are more for developers who want to change the behavior of the controller
77 # at a fundamental level.
78
79 # You should not have to change these attributes.
80
81 # c.Global.url_file_name = u'ipcontroller-engine.furl'
82
83
84
85
@@ -1,165 +0,0 b''
1 # Get the config being loaded so we can set attributes on it
2 c = get_config()
3
4 #-----------------------------------------------------------------------------
5 # Global options
6 #-----------------------------------------------------------------------------
7
8 # c.Global.display_banner = True
9
10 # c.Global.classic = False
11
12 # c.Global.nosep = True
13
14 # If you still use multiple versions of IPytho on the same machine,
15 # set this to True to suppress warnings about old configuration files
16 # c.Global.ignore_old_config = False
17
18 # Set this to determine the detail of what is logged at startup.
19 # The default is 30 and possible values are 0,10,20,30,40,50.
20 # c.Global.log_level = 20
21
22 # This should be a list of importable Python modules that have an
23 # load_ipython_extension(ip) method. This method gets called when the extension
24 # is loaded. You can put your extensions anywhere they can be imported
25 # but we add the extensions subdir of the ipython directory to sys.path
26 # during extension loading, so you can put them there as well.
27 # c.Global.extensions = [
28 # 'myextension'
29 # ]
30
31 # These lines are run in IPython in the user's namespace after extensions
32 # are loaded. They can contain full IPython syntax with magics etc.
33 # c.Global.exec_lines = [
34 # 'import numpy',
35 # 'a = 10; b = 20',
36 # '1/0'
37 # ]
38
39 # These files are run in IPython in the user's namespace. Files with a .py
40 # extension need to be pure Python. Files with a .ipy extension can have
41 # custom IPython syntax (like magics, etc.).
42 # These files need to be in the cwd, the ipython_dir or be absolute paths.
43 # c.Global.exec_files = [
44 # 'mycode.py',
45 # 'fancy.ipy'
46 # ]
47
48 #-----------------------------------------------------------------------------
49 # InteractiveShell options
50 #-----------------------------------------------------------------------------
51
52 # c.InteractiveShell.autocall = 1
53
54 # c.TerminalInteractiveShell.autoedit_syntax = False
55
56 # c.InteractiveShell.autoindent = True
57
58 # c.InteractiveShell.automagic = False
59
60 # c.TerminalTerminalInteractiveShell.banner1 = 'This if for overriding the default IPython banner'
61
62 # c.TerminalTerminalInteractiveShell.banner2 = "This is for extra banner text"
63
64 # c.InteractiveShell.cache_size = 1000
65
66 # c.InteractiveShell.colors = 'LightBG'
67
68 # c.InteractiveShell.color_info = True
69
70 # c.TerminalInteractiveShell.confirm_exit = True
71
72 # c.InteractiveShell.deep_reload = False
73
74 # c.TerminalInteractiveShell.editor = 'nano'
75
76 # c.InteractiveShell.logstart = True
77
78 # c.InteractiveShell.logfile = u'ipython_log.py'
79
80 # c.InteractiveShell.logappend = u'mylog.py'
81
82 # c.InteractiveShell.object_info_string_level = 0
83
84 # c.TerminalInteractiveShell.pager = 'less'
85
86 # c.InteractiveShell.pdb = False
87
88 # c.InteractiveShell.prompt_in1 = 'In [\#]: '
89 # c.InteractiveShell.prompt_in2 = ' .\D.: '
90 # c.InteractiveShell.prompt_out = 'Out[\#]: '
91 # c.InteractiveShell.prompts_pad_left = True
92
93 # c.InteractiveShell.quiet = False
94
95 # c.InteractiveShell.history_length = 10000
96
97 # Readline
98 # c.InteractiveShell.readline_use = True
99
100 # be careful with meta-key ('\M-<x>') bindings, because
101 # they conflict with 8-bit encodings (e.g. UTF8)
102
103 # c.InteractiveShell.readline_parse_and_bind = [
104 # 'tab: complete',
105 # '"\C-l": possible-completions',
106 # 'set show-all-if-ambiguous on',
107 # '"\C-o": tab-insert',
108 # '"\C-r": reverse-search-history',
109 # '"\C-s": forward-search-history',
110 # '"\C-p": history-search-backward',
111 # '"\C-n": history-search-forward',
112 # '"\e[A": history-search-backward',
113 # '"\e[B": history-search-forward',
114 # '"\C-k": kill-line',
115 # '"\C-u": unix-line-discard',
116 # ]
117 # c.InteractiveShell.readline_remove_delims = '-/~'
118 # c.InteractiveShell.readline_merge_completions = True
119 # c.InteractiveShell.readline_omit__names = 0
120
121 # c.TerminalInteractiveShell.screen_length = 0
122
123 # c.InteractiveShell.separate_in = '\n'
124 # c.InteractiveShell.separate_out = ''
125 # c.InteractiveShell.separate_out2 = ''
126
127 # c.TerminalInteractiveShell.term_title = False
128
129 # c.InteractiveShell.wildcards_case_sensitive = True
130
131 # c.InteractiveShell.xmode = 'Context'
132
133 #-----------------------------------------------------------------------------
134 # Formatter and display options
135 #-----------------------------------------------------------------------------
136
137 # c.PlainTextFormatter.pprint = True
138
139 #-----------------------------------------------------------------------------
140 # PrefilterManager options
141 #-----------------------------------------------------------------------------
142
143 # c.PrefilterManager.multi_line_specials = True
144
145 #-----------------------------------------------------------------------------
146 # AliasManager options
147 #-----------------------------------------------------------------------------
148
149 # Do this to disable all defaults
150 # c.AliasManager.default_aliases = []
151
152 # c.AliasManager.user_aliases = [
153 # ('foo', 'echo Hi')
154 # ]
155
156 #-----------------------------------------------------------------------------
157 # HistoryManager options
158 #-----------------------------------------------------------------------------
159
160 # Enable logging output as well as input to the database.
161 # c.HistoryManager.db_log_output = False
162
163 # Only write to the database every n commands - this can save disk
164 # access (and hence power) over the default of writing on every command.
165 # c.HistoryManager.db_cache_size = 0
@@ -1,20 +0,0 b''
1 c = get_config()
2
3 # This can be used at any point in a config file to load a sub config
4 # and merge it into the current one.
5 load_subconfig('ipython_config.py')
6
7 lines = """
8 import numpy
9 import scipy
10 import numpy as np
11 import scipy as sp
12 """
13
14 # You have to make sure that attributes that are containers already
15 # exist before using them. Simple assigning a new list will override
16 # all previous values.
17 if hasattr(c.Global, 'exec_lines'):
18 c.Global.exec_lines.append(lines)
19 else:
20 c.Global.exec_lines = [lines] No newline at end of file
This diff has been collapsed as it changes many lines, (566 lines changed) Show them Hide them
@@ -1,566 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 The IPython cluster directory
5 """
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 from __future__ import with_statement
19
20 import os
21 import logging
22 import re
23 import shutil
24 import sys
25
26 from subprocess import Popen, PIPE
27
28 from IPython.config.loader import PyFileConfigLoader
29 from IPython.config.configurable import Configurable
30 from IPython.core.application import Application, BaseAppConfigLoader
31 from IPython.core.crashhandler import CrashHandler
32 from IPython.core import release
33 from IPython.utils.path import (
34 get_ipython_package_dir,
35 expand_path
36 )
37 from IPython.utils.traitlets import Unicode
38
39 #-----------------------------------------------------------------------------
40 # Module errors
41 #-----------------------------------------------------------------------------
42
43 class ClusterDirError(Exception):
44 pass
45
46
47 class PIDFileError(Exception):
48 pass
49
50
51 #-----------------------------------------------------------------------------
52 # Class for managing cluster directories
53 #-----------------------------------------------------------------------------
54
55 class ClusterDir(Configurable):
56 """An object to manage the cluster directory and its resources.
57
58 The cluster directory is used by :command:`ipengine`,
59 :command:`ipcontroller` and :command:`ipclsuter` to manage the
60 configuration, logging and security of these applications.
61
62 This object knows how to find, create and manage these directories. This
63 should be used by any code that want's to handle cluster directories.
64 """
65
66 security_dir_name = Unicode('security')
67 log_dir_name = Unicode('log')
68 pid_dir_name = Unicode('pid')
69 security_dir = Unicode(u'')
70 log_dir = Unicode(u'')
71 pid_dir = Unicode(u'')
72 location = Unicode(u'')
73
74 def __init__(self, location=u''):
75 super(ClusterDir, self).__init__(location=location)
76
77 def _location_changed(self, name, old, new):
78 if not os.path.isdir(new):
79 os.makedirs(new)
80 self.security_dir = os.path.join(new, self.security_dir_name)
81 self.log_dir = os.path.join(new, self.log_dir_name)
82 self.pid_dir = os.path.join(new, self.pid_dir_name)
83 self.check_dirs()
84
85 def _log_dir_changed(self, name, old, new):
86 self.check_log_dir()
87
88 def check_log_dir(self):
89 if not os.path.isdir(self.log_dir):
90 os.mkdir(self.log_dir)
91
92 def _security_dir_changed(self, name, old, new):
93 self.check_security_dir()
94
95 def check_security_dir(self):
96 if not os.path.isdir(self.security_dir):
97 os.mkdir(self.security_dir, 0700)
98 os.chmod(self.security_dir, 0700)
99
100 def _pid_dir_changed(self, name, old, new):
101 self.check_pid_dir()
102
103 def check_pid_dir(self):
104 if not os.path.isdir(self.pid_dir):
105 os.mkdir(self.pid_dir, 0700)
106 os.chmod(self.pid_dir, 0700)
107
108 def check_dirs(self):
109 self.check_security_dir()
110 self.check_log_dir()
111 self.check_pid_dir()
112
113 def load_config_file(self, filename):
114 """Load a config file from the top level of the cluster dir.
115
116 Parameters
117 ----------
118 filename : unicode or str
119 The filename only of the config file that must be located in
120 the top-level of the cluster directory.
121 """
122 loader = PyFileConfigLoader(filename, self.location)
123 return loader.load_config()
124
125 def copy_config_file(self, config_file, path=None, overwrite=False):
126 """Copy a default config file into the active cluster directory.
127
128 Default configuration files are kept in :mod:`IPython.config.default`.
129 This function moves these from that location to the working cluster
130 directory.
131 """
132 if path is None:
133 import IPython.config.default
134 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
135 path = os.path.sep.join(path)
136 src = os.path.join(path, config_file)
137 dst = os.path.join(self.location, config_file)
138 if not os.path.isfile(dst) or overwrite:
139 shutil.copy(src, dst)
140
141 def copy_all_config_files(self, path=None, overwrite=False):
142 """Copy all config files into the active cluster directory."""
143 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
144 u'ipcluster_config.py']:
145 self.copy_config_file(f, path=path, overwrite=overwrite)
146
147 @classmethod
148 def create_cluster_dir(csl, cluster_dir):
149 """Create a new cluster directory given a full path.
150
151 Parameters
152 ----------
153 cluster_dir : str
154 The full path to the cluster directory. If it does exist, it will
155 be used. If not, it will be created.
156 """
157 return ClusterDir(location=cluster_dir)
158
159 @classmethod
160 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
161 """Create a cluster dir by profile name and path.
162
163 Parameters
164 ----------
165 path : str
166 The path (directory) to put the cluster directory in.
167 profile : str
168 The name of the profile. The name of the cluster directory will
169 be "cluster_<profile>".
170 """
171 if not os.path.isdir(path):
172 raise ClusterDirError('Directory not found: %s' % path)
173 cluster_dir = os.path.join(path, u'cluster_' + profile)
174 return ClusterDir(location=cluster_dir)
175
176 @classmethod
177 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
178 """Find an existing cluster dir by profile name, return its ClusterDir.
179
180 This searches through a sequence of paths for a cluster dir. If it
181 is not found, a :class:`ClusterDirError` exception will be raised.
182
183 The search path algorithm is:
184 1. ``os.getcwd()``
185 2. ``ipython_dir``
186 3. The directories found in the ":" separated
187 :env:`IPCLUSTER_DIR_PATH` environment variable.
188
189 Parameters
190 ----------
191 ipython_dir : unicode or str
192 The IPython directory to use.
193 profile : unicode or str
194 The name of the profile. The name of the cluster directory
195 will be "cluster_<profile>".
196 """
197 dirname = u'cluster_' + profile
198 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
199 if cluster_dir_paths:
200 cluster_dir_paths = cluster_dir_paths.split(':')
201 else:
202 cluster_dir_paths = []
203 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
204 for p in paths:
205 cluster_dir = os.path.join(p, dirname)
206 if os.path.isdir(cluster_dir):
207 return ClusterDir(location=cluster_dir)
208 else:
209 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
210
211 @classmethod
212 def find_cluster_dir(cls, cluster_dir):
213 """Find/create a cluster dir and return its ClusterDir.
214
215 This will create the cluster directory if it doesn't exist.
216
217 Parameters
218 ----------
219 cluster_dir : unicode or str
220 The path of the cluster directory. This is expanded using
221 :func:`IPython.utils.genutils.expand_path`.
222 """
223 cluster_dir = expand_path(cluster_dir)
224 if not os.path.isdir(cluster_dir):
225 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
226 return ClusterDir(location=cluster_dir)
227
228
229 #-----------------------------------------------------------------------------
230 # Command line options
231 #-----------------------------------------------------------------------------
232
233 class ClusterDirConfigLoader(BaseAppConfigLoader):
234
235 def _add_cluster_profile(self, parser):
236 paa = parser.add_argument
237 paa('-p', '--profile',
238 dest='Global.profile',type=unicode,
239 help=
240 """The string name of the profile to be used. This determines the name
241 of the cluster dir as: cluster_<profile>. The default profile is named
242 'default'. The cluster directory is resolve this way if the
243 --cluster-dir option is not used.""",
244 metavar='Global.profile')
245
246 def _add_cluster_dir(self, parser):
247 paa = parser.add_argument
248 paa('--cluster-dir',
249 dest='Global.cluster_dir',type=unicode,
250 help="""Set the cluster dir. This overrides the logic used by the
251 --profile option.""",
252 metavar='Global.cluster_dir')
253
254 def _add_work_dir(self, parser):
255 paa = parser.add_argument
256 paa('--work-dir',
257 dest='Global.work_dir',type=unicode,
258 help='Set the working dir for the process.',
259 metavar='Global.work_dir')
260
261 def _add_clean_logs(self, parser):
262 paa = parser.add_argument
263 paa('--clean-logs',
264 dest='Global.clean_logs', action='store_true',
265 help='Delete old log flies before starting.')
266
267 def _add_no_clean_logs(self, parser):
268 paa = parser.add_argument
269 paa('--no-clean-logs',
270 dest='Global.clean_logs', action='store_false',
271 help="Don't Delete old log flies before starting.")
272
273 def _add_arguments(self):
274 super(ClusterDirConfigLoader, self)._add_arguments()
275 self._add_cluster_profile(self.parser)
276 self._add_cluster_dir(self.parser)
277 self._add_work_dir(self.parser)
278 self._add_clean_logs(self.parser)
279 self._add_no_clean_logs(self.parser)
280
281
282 #-----------------------------------------------------------------------------
283 # Crash handler for this application
284 #-----------------------------------------------------------------------------
285
286
287 _message_template = """\
288 Oops, $self.app_name crashed. We do our best to make it stable, but...
289
290 A crash report was automatically generated with the following information:
291 - A verbatim copy of the crash traceback.
292 - Data on your current $self.app_name configuration.
293
294 It was left in the file named:
295 \t'$self.crash_report_fname'
296 If you can email this file to the developers, the information in it will help
297 them in understanding and correcting the problem.
298
299 You can mail it to: $self.contact_name at $self.contact_email
300 with the subject '$self.app_name Crash Report'.
301
302 If you want to do it now, the following command will work (under Unix):
303 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
304
305 To ensure accurate tracking of this issue, please file a report about it at:
306 $self.bug_tracker
307 """
308
309 class ClusterDirCrashHandler(CrashHandler):
310 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
311
312 message_template = _message_template
313
314 def __init__(self, app):
315 contact_name = release.authors['Brian'][0]
316 contact_email = release.authors['Brian'][1]
317 bug_tracker = 'http://github.com/ipython/ipython/issues'
318 super(ClusterDirCrashHandler,self).__init__(
319 app, contact_name, contact_email, bug_tracker
320 )
321
322
323 #-----------------------------------------------------------------------------
324 # Main application
325 #-----------------------------------------------------------------------------
326
327 class ApplicationWithClusterDir(Application):
328 """An application that puts everything into a cluster directory.
329
330 Instead of looking for things in the ipython_dir, this type of application
331 will use its own private directory called the "cluster directory"
332 for things like config files, log files, etc.
333
334 The cluster directory is resolved as follows:
335
336 * If the ``--cluster-dir`` option is given, it is used.
337 * If ``--cluster-dir`` is not given, the application directory is
338 resolve using the profile name as ``cluster_<profile>``. The search
339 path for this directory is then i) cwd if it is found there
340 and ii) in ipython_dir otherwise.
341
342 The config file for the application is to be put in the cluster
343 dir and named the value of the ``config_file_name`` class attribute.
344 """
345
346 command_line_loader = ClusterDirConfigLoader
347 crash_handler_class = ClusterDirCrashHandler
348 auto_create_cluster_dir = True
349 # temporarily override default_log_level to INFO
350 default_log_level = logging.INFO
351
352 def create_default_config(self):
353 super(ApplicationWithClusterDir, self).create_default_config()
354 self.default_config.Global.profile = u'default'
355 self.default_config.Global.cluster_dir = u''
356 self.default_config.Global.work_dir = os.getcwd()
357 self.default_config.Global.log_to_file = False
358 self.default_config.Global.log_url = None
359 self.default_config.Global.clean_logs = False
360
361 def find_resources(self):
362 """This resolves the cluster directory.
363
364 This tries to find the cluster directory and if successful, it will
365 have done:
366 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
367 the application.
368 * Sets ``self.cluster_dir`` attribute of the application and config
369 objects.
370
371 The algorithm used for this is as follows:
372 1. Try ``Global.cluster_dir``.
373 2. Try using ``Global.profile``.
374 3. If both of these fail and ``self.auto_create_cluster_dir`` is
375 ``True``, then create the new cluster dir in the IPython directory.
376 4. If all fails, then raise :class:`ClusterDirError`.
377 """
378
379 try:
380 cluster_dir = self.command_line_config.Global.cluster_dir
381 except AttributeError:
382 cluster_dir = self.default_config.Global.cluster_dir
383 cluster_dir = expand_path(cluster_dir)
384 try:
385 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
386 except ClusterDirError:
387 pass
388 else:
389 self.log.info('Using existing cluster dir: %s' % \
390 self.cluster_dir_obj.location
391 )
392 self.finish_cluster_dir()
393 return
394
395 try:
396 self.profile = self.command_line_config.Global.profile
397 except AttributeError:
398 self.profile = self.default_config.Global.profile
399 try:
400 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
401 self.ipython_dir, self.profile)
402 except ClusterDirError:
403 pass
404 else:
405 self.log.info('Using existing cluster dir: %s' % \
406 self.cluster_dir_obj.location
407 )
408 self.finish_cluster_dir()
409 return
410
411 if self.auto_create_cluster_dir:
412 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
413 self.ipython_dir, self.profile
414 )
415 self.log.info('Creating new cluster dir: %s' % \
416 self.cluster_dir_obj.location
417 )
418 self.finish_cluster_dir()
419 else:
420 raise ClusterDirError('Could not find a valid cluster directory.')
421
422 def finish_cluster_dir(self):
423 # Set the cluster directory
424 self.cluster_dir = self.cluster_dir_obj.location
425
426 # These have to be set because they could be different from the one
427 # that we just computed. Because command line has the highest
428 # priority, this will always end up in the master_config.
429 self.default_config.Global.cluster_dir = self.cluster_dir
430 self.command_line_config.Global.cluster_dir = self.cluster_dir
431
432 def find_config_file_name(self):
433 """Find the config file name for this application."""
434 # For this type of Application it should be set as a class attribute.
435 if not hasattr(self, 'default_config_file_name'):
436 self.log.critical("No config filename found")
437 else:
438 self.config_file_name = self.default_config_file_name
439
440 def find_config_file_paths(self):
441 # Set the search path to to the cluster directory. We should NOT
442 # include IPython.config.default here as the default config files
443 # are ALWAYS automatically moved to the cluster directory.
444 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
445 self.config_file_paths = (self.cluster_dir,)
446
447 def pre_construct(self):
448 # The log and security dirs were set earlier, but here we put them
449 # into the config and log them.
450 config = self.master_config
451 sdir = self.cluster_dir_obj.security_dir
452 self.security_dir = config.Global.security_dir = sdir
453 ldir = self.cluster_dir_obj.log_dir
454 self.log_dir = config.Global.log_dir = ldir
455 pdir = self.cluster_dir_obj.pid_dir
456 self.pid_dir = config.Global.pid_dir = pdir
457 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
458 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
459 # Change to the working directory. We do this just before construct
460 # is called so all the components there have the right working dir.
461 self.to_work_dir()
462
463 def to_work_dir(self):
464 wd = self.master_config.Global.work_dir
465 if unicode(wd) != unicode(os.getcwd()):
466 os.chdir(wd)
467 self.log.info("Changing to working dir: %s" % wd)
468
469 def start_logging(self):
470 # Remove old log files
471 if self.master_config.Global.clean_logs:
472 log_dir = self.master_config.Global.log_dir
473 for f in os.listdir(log_dir):
474 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
475 # if f.startswith(self.name + u'-') and f.endswith('.log'):
476 os.remove(os.path.join(log_dir, f))
477 # Start logging to the new log file
478 if self.master_config.Global.log_to_file:
479 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
480 logfile = os.path.join(self.log_dir, log_filename)
481 open_log_file = open(logfile, 'w')
482 elif self.master_config.Global.log_url:
483 open_log_file = None
484 else:
485 open_log_file = sys.stdout
486 if open_log_file is not None:
487 self.log.removeHandler(self._log_handler)
488 self._log_handler = logging.StreamHandler(open_log_file)
489 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
490 self._log_handler.setFormatter(self._log_formatter)
491 self.log.addHandler(self._log_handler)
492 # log.startLogging(open_log_file)
493
494 def write_pid_file(self, overwrite=False):
495 """Create a .pid file in the pid_dir with my pid.
496
497 This must be called after pre_construct, which sets `self.pid_dir`.
498 This raises :exc:`PIDFileError` if the pid file exists already.
499 """
500 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
501 if os.path.isfile(pid_file):
502 pid = self.get_pid_from_file()
503 if not overwrite:
504 raise PIDFileError(
505 'The pid file [%s] already exists. \nThis could mean that this '
506 'server is already running with [pid=%s].' % (pid_file, pid)
507 )
508 with open(pid_file, 'w') as f:
509 self.log.info("Creating pid file: %s" % pid_file)
510 f.write(repr(os.getpid())+'\n')
511
512 def remove_pid_file(self):
513 """Remove the pid file.
514
515 This should be called at shutdown by registering a callback with
516 :func:`reactor.addSystemEventTrigger`. This needs to return
517 ``None``.
518 """
519 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
520 if os.path.isfile(pid_file):
521 try:
522 self.log.info("Removing pid file: %s" % pid_file)
523 os.remove(pid_file)
524 except:
525 self.log.warn("Error removing the pid file: %s" % pid_file)
526
527 def get_pid_from_file(self):
528 """Get the pid from the pid file.
529
530 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
531 """
532 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
533 if os.path.isfile(pid_file):
534 with open(pid_file, 'r') as f:
535 pid = int(f.read().strip())
536 return pid
537 else:
538 raise PIDFileError('pid file not found: %s' % pid_file)
539
540 def check_pid(self, pid):
541 if os.name == 'nt':
542 try:
543 import ctypes
544 # returns 0 if no such process (of ours) exists
545 # positive int otherwise
546 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
547 except Exception:
548 self.log.warn(
549 "Could not determine whether pid %i is running via `OpenProcess`. "
550 " Making the likely assumption that it is."%pid
551 )
552 return True
553 return bool(p)
554 else:
555 try:
556 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
557 output,_ = p.communicate()
558 except OSError:
559 self.log.warn(
560 "Could not determine whether pid %i is running via `ps x`. "
561 " Making the likely assumption that it is."%pid
562 )
563 return True
564 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
565 return pid in pids
566 No newline at end of file
@@ -1,116 +0,0 b''
1 #!/usr/bin/env python
2 """The IPython Controller with 0MQ
3 This is a collection of one Hub and several Schedulers.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
16
17 from multiprocessing import Process
18
19 import zmq
20 from zmq.devices import ProcessMonitoredQueue
21 # internal:
22 from IPython.utils.importstring import import_item
23 from IPython.utils.traitlets import Int, CStr, Instance, List, Bool
24
25 from IPython.parallel.util import signal_children
26 from .hub import Hub, HubFactory
27 from .scheduler import launch_scheduler
28
29 #-----------------------------------------------------------------------------
30 # Configurable
31 #-----------------------------------------------------------------------------
32
33
34 class ControllerFactory(HubFactory):
35 """Configurable for setting up a Hub and Schedulers."""
36
37 usethreads = Bool(False, config=True)
38
39 # internal
40 children = List()
41 mq_class = CStr('zmq.devices.ProcessMonitoredQueue')
42
43 def _usethreads_changed(self, name, old, new):
44 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
45
46 def __init__(self, **kwargs):
47 super(ControllerFactory, self).__init__(**kwargs)
48 self.subconstructors.append(self.construct_schedulers)
49
50 def start(self):
51 super(ControllerFactory, self).start()
52 child_procs = []
53 for child in self.children:
54 child.start()
55 if isinstance(child, ProcessMonitoredQueue):
56 child_procs.append(child.launcher)
57 elif isinstance(child, Process):
58 child_procs.append(child)
59 if child_procs:
60 signal_children(child_procs)
61
62
63 def construct_schedulers(self):
64 children = self.children
65 mq = import_item(self.mq_class)
66
67 # maybe_inproc = 'inproc://monitor' if self.usethreads else self.monitor_url
68 # IOPub relay (in a Process)
69 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, 'N/A','iopub')
70 q.bind_in(self.client_info['iopub'])
71 q.bind_out(self.engine_info['iopub'])
72 q.setsockopt_out(zmq.SUBSCRIBE, '')
73 q.connect_mon(self.monitor_url)
74 q.daemon=True
75 children.append(q)
76
77 # Multiplexer Queue (in a Process)
78 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, 'in', 'out')
79 q.bind_in(self.client_info['mux'])
80 q.setsockopt_in(zmq.IDENTITY, 'mux')
81 q.bind_out(self.engine_info['mux'])
82 q.connect_mon(self.monitor_url)
83 q.daemon=True
84 children.append(q)
85
86 # Control Queue (in a Process)
87 q = mq(zmq.XREP, zmq.XREP, zmq.PUB, 'incontrol', 'outcontrol')
88 q.bind_in(self.client_info['control'])
89 q.setsockopt_in(zmq.IDENTITY, 'control')
90 q.bind_out(self.engine_info['control'])
91 q.connect_mon(self.monitor_url)
92 q.daemon=True
93 children.append(q)
94 # Task Queue (in a Process)
95 if self.scheme == 'pure':
96 self.log.warn("task::using pure XREQ Task scheduler")
97 q = mq(zmq.XREP, zmq.XREQ, zmq.PUB, 'intask', 'outtask')
98 q.bind_in(self.client_info['task'][1])
99 q.setsockopt_in(zmq.IDENTITY, 'task')
100 q.bind_out(self.engine_info['task'])
101 q.connect_mon(self.monitor_url)
102 q.daemon=True
103 children.append(q)
104 elif self.scheme == 'none':
105 self.log.warn("task::using no Task scheduler")
106
107 else:
108 self.log.info("task::using Python %s Task scheduler"%self.scheme)
109 sargs = (self.client_info['task'][1], self.engine_info['task'],
110 self.monitor_url, self.client_info['notification'])
111 kwargs = dict(scheme=self.scheme,logname=self.log.name, loglevel=self.log.level,
112 config=dict(self.config))
113 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
114 q.daemon=True
115 children.append(q)
116
@@ -1,419 +0,0 b''
1 #!/usr/bin/env python
2 """edited session.py to work with streams, and move msg_type to the header
3 """
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2010-2011 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11
12 import os
13 import pprint
14 import uuid
15 from datetime import datetime
16
17 try:
18 import cPickle
19 pickle = cPickle
20 except:
21 cPickle = None
22 import pickle
23
24 import zmq
25 from zmq.utils import jsonapi
26 from zmq.eventloop.zmqstream import ZMQStream
27
28 from .util import ISO8601
29
30 def squash_unicode(obj):
31 """coerce unicode back to bytestrings."""
32 if isinstance(obj,dict):
33 for key in obj.keys():
34 obj[key] = squash_unicode(obj[key])
35 if isinstance(key, unicode):
36 obj[squash_unicode(key)] = obj.pop(key)
37 elif isinstance(obj, list):
38 for i,v in enumerate(obj):
39 obj[i] = squash_unicode(v)
40 elif isinstance(obj, unicode):
41 obj = obj.encode('utf8')
42 return obj
43
44 def _date_default(obj):
45 if isinstance(obj, datetime):
46 return obj.strftime(ISO8601)
47 else:
48 raise TypeError("%r is not JSON serializable"%obj)
49
50 _default_key = 'on_unknown' if jsonapi.jsonmod.__name__ == 'jsonlib' else 'default'
51 json_packer = lambda obj: jsonapi.dumps(obj, **{_default_key:_date_default})
52 json_unpacker = lambda s: squash_unicode(jsonapi.loads(s))
53
54 pickle_packer = lambda o: pickle.dumps(o,-1)
55 pickle_unpacker = pickle.loads
56
57 default_packer = json_packer
58 default_unpacker = json_unpacker
59
60
61 DELIM="<IDS|MSG>"
62
63 class Message(object):
64 """A simple message object that maps dict keys to attributes.
65
66 A Message can be created from a dict and a dict from a Message instance
67 simply by calling dict(msg_obj)."""
68
69 def __init__(self, msg_dict):
70 dct = self.__dict__
71 for k, v in dict(msg_dict).iteritems():
72 if isinstance(v, dict):
73 v = Message(v)
74 dct[k] = v
75
76 # Having this iterator lets dict(msg_obj) work out of the box.
77 def __iter__(self):
78 return iter(self.__dict__.iteritems())
79
80 def __repr__(self):
81 return repr(self.__dict__)
82
83 def __str__(self):
84 return pprint.pformat(self.__dict__)
85
86 def __contains__(self, k):
87 return k in self.__dict__
88
89 def __getitem__(self, k):
90 return self.__dict__[k]
91
92
93 def msg_header(msg_id, msg_type, username, session):
94 date=datetime.now().strftime(ISO8601)
95 return locals()
96
97 def extract_header(msg_or_header):
98 """Given a message or header, return the header."""
99 if not msg_or_header:
100 return {}
101 try:
102 # See if msg_or_header is the entire message.
103 h = msg_or_header['header']
104 except KeyError:
105 try:
106 # See if msg_or_header is just the header
107 h = msg_or_header['msg_id']
108 except KeyError:
109 raise
110 else:
111 h = msg_or_header
112 if not isinstance(h, dict):
113 h = dict(h)
114 return h
115
116 class StreamSession(object):
117 """tweaked version of IPython.zmq.session.Session, for development in Parallel"""
118 debug=False
119 key=None
120
121 def __init__(self, username=None, session=None, packer=None, unpacker=None, key=None, keyfile=None):
122 if username is None:
123 username = os.environ.get('USER','username')
124 self.username = username
125 if session is None:
126 self.session = str(uuid.uuid4())
127 else:
128 self.session = session
129 self.msg_id = str(uuid.uuid4())
130 if packer is None:
131 self.pack = default_packer
132 else:
133 if not callable(packer):
134 raise TypeError("packer must be callable, not %s"%type(packer))
135 self.pack = packer
136
137 if unpacker is None:
138 self.unpack = default_unpacker
139 else:
140 if not callable(unpacker):
141 raise TypeError("unpacker must be callable, not %s"%type(unpacker))
142 self.unpack = unpacker
143
144 if key is not None and keyfile is not None:
145 raise TypeError("Must specify key OR keyfile, not both")
146 if keyfile is not None:
147 with open(keyfile) as f:
148 self.key = f.read().strip()
149 else:
150 self.key = key
151 if isinstance(self.key, unicode):
152 self.key = self.key.encode('utf8')
153 # print key, keyfile, self.key
154 self.none = self.pack({})
155
156 def msg_header(self, msg_type):
157 h = msg_header(self.msg_id, msg_type, self.username, self.session)
158 self.msg_id = str(uuid.uuid4())
159 return h
160
161 def msg(self, msg_type, content=None, parent=None, subheader=None):
162 msg = {}
163 msg['header'] = self.msg_header(msg_type)
164 msg['msg_id'] = msg['header']['msg_id']
165 msg['parent_header'] = {} if parent is None else extract_header(parent)
166 msg['msg_type'] = msg_type
167 msg['content'] = {} if content is None else content
168 sub = {} if subheader is None else subheader
169 msg['header'].update(sub)
170 return msg
171
172 def check_key(self, msg_or_header):
173 """Check that a message's header has the right key"""
174 if self.key is None:
175 return True
176 header = extract_header(msg_or_header)
177 return header.get('key', None) == self.key
178
179
180 def serialize(self, msg, ident=None):
181 content = msg.get('content', {})
182 if content is None:
183 content = self.none
184 elif isinstance(content, dict):
185 content = self.pack(content)
186 elif isinstance(content, bytes):
187 # content is already packed, as in a relayed message
188 pass
189 elif isinstance(content, unicode):
190 # should be bytes, but JSON often spits out unicode
191 content = content.encode('utf8')
192 else:
193 raise TypeError("Content incorrect type: %s"%type(content))
194
195 to_send = []
196
197 if isinstance(ident, list):
198 # accept list of idents
199 to_send.extend(ident)
200 elif ident is not None:
201 to_send.append(ident)
202 to_send.append(DELIM)
203 if self.key is not None:
204 to_send.append(self.key)
205 to_send.append(self.pack(msg['header']))
206 to_send.append(self.pack(msg['parent_header']))
207 to_send.append(content)
208
209 return to_send
210
211 def send(self, stream, msg_or_type, content=None, buffers=None, parent=None, subheader=None, ident=None, track=False):
212 """Build and send a message via stream or socket.
213
214 Parameters
215 ----------
216
217 stream : zmq.Socket or ZMQStream
218 the socket-like object used to send the data
219 msg_or_type : str or Message/dict
220 Normally, msg_or_type will be a msg_type unless a message is being sent more
221 than once.
222
223 content : dict or None
224 the content of the message (ignored if msg_or_type is a message)
225 buffers : list or None
226 the already-serialized buffers to be appended to the message
227 parent : Message or dict or None
228 the parent or parent header describing the parent of this message
229 subheader : dict or None
230 extra header keys for this message's header
231 ident : bytes or list of bytes
232 the zmq.IDENTITY routing path
233 track : bool
234 whether to track. Only for use with Sockets, because ZMQStream objects cannot track messages.
235
236 Returns
237 -------
238 msg : message dict
239 the constructed message
240 (msg,tracker) : (message dict, MessageTracker)
241 if track=True, then a 2-tuple will be returned, the first element being the constructed
242 message, and the second being the MessageTracker
243
244 """
245
246 if not isinstance(stream, (zmq.Socket, ZMQStream)):
247 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
248 elif track and isinstance(stream, ZMQStream):
249 raise TypeError("ZMQStream cannot track messages")
250
251 if isinstance(msg_or_type, (Message, dict)):
252 # we got a Message, not a msg_type
253 # don't build a new Message
254 msg = msg_or_type
255 else:
256 msg = self.msg(msg_or_type, content, parent, subheader)
257
258 buffers = [] if buffers is None else buffers
259 to_send = self.serialize(msg, ident)
260 flag = 0
261 if buffers:
262 flag = zmq.SNDMORE
263 _track = False
264 else:
265 _track=track
266 if track:
267 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
268 else:
269 tracker = stream.send_multipart(to_send, flag, copy=False)
270 for b in buffers[:-1]:
271 stream.send(b, flag, copy=False)
272 if buffers:
273 if track:
274 tracker = stream.send(buffers[-1], copy=False, track=track)
275 else:
276 tracker = stream.send(buffers[-1], copy=False)
277
278 # omsg = Message(msg)
279 if self.debug:
280 pprint.pprint(msg)
281 pprint.pprint(to_send)
282 pprint.pprint(buffers)
283
284 msg['tracker'] = tracker
285
286 return msg
287
288 def send_raw(self, stream, msg, flags=0, copy=True, ident=None):
289 """Send a raw message via ident path.
290
291 Parameters
292 ----------
293 msg : list of sendable buffers"""
294 to_send = []
295 if isinstance(ident, bytes):
296 ident = [ident]
297 if ident is not None:
298 to_send.extend(ident)
299 to_send.append(DELIM)
300 if self.key is not None:
301 to_send.append(self.key)
302 to_send.extend(msg)
303 stream.send_multipart(msg, flags, copy=copy)
304
305 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
306 """receives and unpacks a message
307 returns [idents], msg"""
308 if isinstance(socket, ZMQStream):
309 socket = socket.socket
310 try:
311 msg = socket.recv_multipart(mode, copy=copy)
312 except zmq.ZMQError as e:
313 if e.errno == zmq.EAGAIN:
314 # We can convert EAGAIN to None as we know in this case
315 # recv_multipart won't return None.
316 return None
317 else:
318 raise
319 # return an actual Message object
320 # determine the number of idents by trying to unpack them.
321 # this is terrible:
322 idents, msg = self.feed_identities(msg, copy)
323 try:
324 return idents, self.unpack_message(msg, content=content, copy=copy)
325 except Exception as e:
326 print (idents, msg)
327 # TODO: handle it
328 raise e
329
330 def feed_identities(self, msg, copy=True):
331 """feed until DELIM is reached, then return the prefix as idents and remainder as
332 msg. This is easily broken by setting an IDENT to DELIM, but that would be silly.
333
334 Parameters
335 ----------
336 msg : a list of Message or bytes objects
337 the message to be split
338 copy : bool
339 flag determining whether the arguments are bytes or Messages
340
341 Returns
342 -------
343 (idents,msg) : two lists
344 idents will always be a list of bytes - the indentity prefix
345 msg will be a list of bytes or Messages, unchanged from input
346 msg should be unpackable via self.unpack_message at this point.
347 """
348 ikey = int(self.key is not None)
349 minlen = 3 + ikey
350 msg = list(msg)
351 idents = []
352 while len(msg) > minlen:
353 if copy:
354 s = msg[0]
355 else:
356 s = msg[0].bytes
357 if s == DELIM:
358 msg.pop(0)
359 break
360 else:
361 idents.append(s)
362 msg.pop(0)
363
364 return idents, msg
365
366 def unpack_message(self, msg, content=True, copy=True):
367 """Return a message object from the format
368 sent by self.send.
369
370 Parameters:
371 -----------
372
373 content : bool (True)
374 whether to unpack the content dict (True),
375 or leave it serialized (False)
376
377 copy : bool (True)
378 whether to return the bytes (True),
379 or the non-copying Message object in each place (False)
380
381 """
382 ikey = int(self.key is not None)
383 minlen = 3 + ikey
384 message = {}
385 if not copy:
386 for i in range(minlen):
387 msg[i] = msg[i].bytes
388 if ikey:
389 if not self.key == msg[0]:
390 raise KeyError("Invalid Session Key: %s"%msg[0])
391 if not len(msg) >= minlen:
392 raise TypeError("malformed message, must have at least %i elements"%minlen)
393 message['header'] = self.unpack(msg[ikey+0])
394 message['msg_type'] = message['header']['msg_type']
395 message['parent_header'] = self.unpack(msg[ikey+1])
396 if content:
397 message['content'] = self.unpack(msg[ikey+2])
398 else:
399 message['content'] = msg[ikey+2]
400
401 message['buffers'] = msg[ikey+3:]# [ m.buffer for m in msg[3:] ]
402 return message
403
404
405 def test_msg2obj():
406 am = dict(x=1)
407 ao = Message(am)
408 assert ao.x == am['x']
409
410 am['y'] = dict(z=1)
411 ao = Message(am)
412 assert ao.y.z == am['y']['z']
413
414 k1, k2 = 'y', 'z'
415 assert ao[k1][k2] == am[k1][k2]
416
417 am2 = dict(ao)
418 assert am['x'] == am2['x']
419 assert am['y']['z'] == am2['y']['z']
@@ -1,6 +0,0 b''
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from IPython.frontend.qt.console.ipythonqt import main
5
6 main()
General Comments 0
You need to be logged in to leave comments. Login now