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 | ||
|
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. |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
""" |
|
|
186 | """Print the alias part of the help.""" | |
|
135 | 187 | if not self.aliases: |
|
136 | 188 | return |
|
137 |
|
|
|
138 |
|
|
|
139 | print "-------" | |
|
140 | print self.alias_description | |
|
141 | ||
|
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 |
|
|
153 | print alias, "(%s)"%longname, ':', trait.__class__.__name__ | |
|
154 |
|
|
|
155 | print indent(help) | |
|
156 | ||
|
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 |
""" |
|
|
215 | """Print the flag part of the help.""" | |
|
160 | 216 | if not self.flags: |
|
161 | 217 | return |
|
162 | 218 | |
|
163 |
|
|
|
164 | print "-----" | |
|
165 | print self.flag_description | |
|
166 | ||
|
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 |
|
|
|
170 |
|
|
|
171 | ||
|
227 | lines.append('--'+m) | |
|
228 | lines.append(indent(dedent(help.strip()))) | |
|
229 | lines.append('') | |
|
230 | print '\n'.join(lines) | |
|
172 | 231 | |
|
173 |
def print_ |
|
|
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 | ||
|
182 | 254 | |
|
183 |
f |
|
|
184 |
|
|
|
255 | if classes: | |
|
256 | if self.classes: | |
|
257 | print "Class parameters" | |
|
258 | print "----------------" | |
|
259 | ||
|
260 | for p in wrap_paragraphs(self.keyvalue_description): | |
|
261 | print p | |
|
262 | ||
|
263 | ||
|
264 | for cls in self.classes: | |
|
265 | cls.class_print_help() | |
|
266 | ||
|
267 | else: | |
|
268 | print "To see all available configurables, use `--help-all`" | |
|
185 | 269 | |
|
186 | 270 | |
|
187 | 271 | def print_description(self): |
|
188 | 272 | """Print the application description.""" |
|
189 |
|
|
|
190 | ||
|
273 | for p in wrap_paragraphs(self.description): | |
|
274 | print p | |
|
275 | ||
|
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 |
s |
|
|
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 |
s |
|
|
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-201 |
|
|
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, |
|
|
150 |
help = v |
|
|
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 |
# |
|
|
206 | for subclass in cls.mro(): | |
|
207 |
|
|
|
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-20 |
|
|
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. |
|
|
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 |
|
|
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 |
|
|
439 | raise ArgumentError("Unrecognized flag: %r"%item) | |
|
407 | 440 | elif isinstance(cfg, (dict, Config)): |
|
408 |
# |
|
|
409 |
|
|
|
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. |
|
|
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( |
|
|
15 |
|
|
|
15 | if hasattr(app, 'exec_lines'): | |
|
16 | app.exec_lines.append(lines) | |
|
16 | 17 | else: |
|
17 |
|
|
|
18 | app.exec_lines = [lines] | |
|
18 | 19 | |
|
19 | 20 | # Load the parallelmagic extension to enable %result, %px, %autopx magics. |
|
20 |
if hasattr( |
|
|
21 |
|
|
|
21 | if hasattr(app, 'extensions'): | |
|
22 | app.extensions.append('parallelmagic') | |
|
22 | 23 | else: |
|
23 |
|
|
|
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 |
|
|
|
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(' |
|
|
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( |
|
|
20 |
|
|
|
20 | if hasattr(app, 'exec_lines'): | |
|
21 | app.exec_lines.append(lines) | |
|
21 | 22 | else: |
|
22 |
|
|
|
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( |
|
|
27 |
|
|
|
27 | if hasattr(app, 'exec_lines'): | |
|
28 | app.exec_lines.append(lines) | |
|
28 | 29 | else: |
|
29 |
|
|
|
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( |
|
|
20 |
|
|
|
20 | if hasattr(app, 'exec_lines'): | |
|
21 | app.exec_lines.append(lines) | |
|
21 | 22 | else: |
|
22 |
|
|
|
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( |
|
|
26 |
|
|
|
26 | if hasattr(app, 'extensions'): | |
|
27 | app.extensions.append('sympyprinting') | |
|
27 | 28 | else: |
|
28 |
|
|
|
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 |
|
|
|
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. |
|
|
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_ |
|
|
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-20 |
|
|
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 |
|
|
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 |
|
|
|
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 |
|
|
195 | if self.config_file_specified: | |
|
383 | 196 | self.log.warn("Config file not found, skipping: %s" % |
|
384 |
self.config_file_name |
|
|
385 | self.file_config = Config() | |
|
197 | self.config_file_name) | |
|
386 | 198 | except: |
|
387 |
|
|
|
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 |
|
|
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 |
|
|
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 |
|
|
|
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. |
|
|
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. |
|
|
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 |
|
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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, |
|
|
185 |
print '[stdout:%i]'%eid, stdout |
|
|
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. |
|
|
34 |
old_manager. |
|
|
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. |
|
|
54 |
kernel_manager. |
|
|
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 |
|
|
|
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 |
|
|
|
623 | fallback = 'Courier' | |
|
595 | 624 | elif sys.platform == 'darwin': |
|
596 |
# OSX always has Monaco |
|
|
597 |
|
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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 |
|
|
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 |
# |
|
|
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 |
|
|
|
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 |
|
|
386 | if self.stylesheet: | |
|
258 | 387 | # we got an expicit stylesheet |
|
259 |
if os.path.isfile( |
|
|
260 |
with open( |
|
|
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."% |
|
|
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 |
# |
|
|
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 |
|
|
|
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 |
# 'Z |
|
|
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 Qt |
|
|
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 |
# ' |
|
|
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 |
# 'Qt |
|
|
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 Qt |
|
|
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 |
# ' |
|
|
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 |
|
|
|
194 |
|
|
|
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._ |
|
|
207 |
self._ |
|
|
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 |
|
|
225 | def shell_channel(self): | |
|
226 | 226 | """ Reimplemented for proper heartbeat management. |
|
227 | 227 | """ |
|
228 |
if self._ |
|
|
229 |
self._ |
|
|
230 |
self._ |
|
|
231 |
return self._ |
|
|
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, |
|
|
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 |
|
|
41 |
from IPython. |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
305 | if self.pylab: | |
|
524 | 306 | activate = self.shell.enable_pylab |
|
525 |
if |
|
|
307 | if self.pylab == 'auto': | |
|
526 | 308 | gui = None |
|
527 | 309 | else: |
|
528 |
gui = |
|
|
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 |
|
|
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, |
|
|
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 |
|
|
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 |
|
|
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 = [' |
|
|
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 |
|
|
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_ |
|
|
261 | from IPython.zmq.pylab.backend_inline import flush_figures | |
|
233 | 262 | from matplotlib import pyplot |
|
234 |
shell.register_post_execute(flush_ |
|
|
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 |
|
|
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 |
|
|
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-20 |
|
|
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. |
|
|
32 | ApplicationWithClusterDir, ClusterDirConfigLoader, | |
|
33 |
|
|
|
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 |
|
|
53 |
you will typically do 'ipcluster create |
|
|
54 |
configuration files, followed by 'ipcluster start |
|
|
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 IPCluster |
|
|
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 = |
|
|
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 |
|
|
421 | ||
|
422 |
def start_engines(self |
|
|
423 |
|
|
|
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 |
|
|
438 |
|
|
|
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. |
|
|
473 |
log_dir = self. |
|
|
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(IP |
|
|
479 | ||
|
480 |
def start |
|
|
481 |
"""Start the app |
|
|
482 | subcmd = self.master_config.Global.subcommand | |
|
483 | if subcmd=='create' or subcmd=='list': | |
|
484 |
|
|
|
485 | elif subcmd=='start': | |
|
486 | self.start_app_start() | |
|
487 | elif subcmd=='stop': | |
|
488 | self.start_app_stop() | |
|
489 | elif subcmd=='engines': | |
|
490 |
|
|
|
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]' % |
|
|
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 |
|
|
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. |
|
|
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 | ||
|
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-20 |
|
|
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. |
|
|
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. |
|
|
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 " |
|
|
63 |
and |
|
|
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. |
|
|
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( |
|
|
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. |
|
|
206 | c = self.config | |
|
312 | 207 | # load from engine config |
|
313 |
with open(os.path.join( |
|
|
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.Session |
|
|
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 |
|
|
|
216 | self.location = cfg['location'] | |
|
322 | 217 | |
|
323 | 218 | # load client config |
|
324 |
with open(os.path.join( |
|
|
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 |
|
|
|
226 | self.ssh_server = cfg['ssh'] | |
|
332 | 227 | assert int(ports) == c.HubFactory.regport, "regport mismatch" |
|
333 | 228 | |
|
334 |
def |
|
|
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 = |
|
|
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 |
|
|
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 |
|
|
|
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.Session |
|
|
358 | key = None | |
|
250 | key = c.Session.key = '' | |
|
359 | 251 | |
|
360 | 252 | try: |
|
361 |
self.factory = |
|
|
362 | self.start_logging() | |
|
363 |
self.factory. |
|
|
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' : |
|
|
264 | 'ssh' : self.ssh_server, | |
|
373 | 265 | 'url' : "%s://%s:%s"%(f.client_transport, f.client_ip, f.regport), |
|
374 |
'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. |
|
|
337 | c = self.config | |
|
385 | 338 | |
|
386 |
sec_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. |
|
|
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 |
|
|
406 | super(IPControllerApp, self).start_logging() | |
|
407 | if self.master_config.Global.log_url: | |
|
408 |
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. |
|
|
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-20 |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
176 | config = self.config | |
|
202 | 177 | # Find the actual controller key file |
|
203 |
if not |
|
|
204 |
|
|
|
205 |
|
|
|
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. |
|
|
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( |
|
|
218 |
with open( |
|
|
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.Session |
|
|
199 | config.Session.key = d['exec_key'] | |
|
225 | 200 | d['url'] = disambiguate_url(d['url'], d['location']) |
|
226 |
config. |
|
|
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 Engine |
|
|
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, log |
|
|
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 |
|
|
|
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. |
|
|
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 |
|
|
|
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 |
|
|
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. |
|
|
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. |
|
|
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 " |
|
|
43 |
See the |
|
|
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. |
|
|
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 |
|
|
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-20 |
|
|
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. |
|
|
53 |
|
|
|
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(Logging |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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',' |
|
|
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, |
|
|
339 |
"""Start the controller by |
|
|
340 |
self.controller_args.extend([' |
|
|
341 |
self. |
|
|
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, |
|
|
359 |
"""Start the engine by |
|
|
360 |
self.engine_args.extend([' |
|
|
361 |
self. |
|
|
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',' |
|
|
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, |
|
|
385 |
"""Start n engines by profile or |
|
|
386 |
self. |
|
|
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, log |
|
|
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( |
|
|
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, |
|
|
476 |
"""Start the controller by |
|
|
477 |
self.controller_args.extend([' |
|
|
478 |
self. |
|
|
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',' |
|
|
494 | ['--log-to-file','log_level=%i'%logging.INFO], config=True, | |
|
495 | help="Command line arguments for ipengine." | |
|
493 | 496 | ) |
|
494 |
n = Int(1 |
|
|
497 | n = Int(1) | |
|
495 | 498 | |
|
496 |
def start(self, n, |
|
|
497 |
"""Start n engines by profile or |
|
|
498 |
self.program_args.extend([' |
|
|
499 |
self. |
|
|
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 |
|
|
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 |
|
|
|
521 | program_args = List([], config=True) | |
|
522 |
|
|
|
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, |
|
|
540 |
self. |
|
|
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',' |
|
|
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',' |
|
|
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, |
|
|
575 |
"""Start engines by profile or |
|
|
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. |
|
|
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, log |
|
|
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( |
|
|
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 = |
|
|
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 = |
|
|
635 | # The hostname of the scheduler to submit the job to | |
|
636 | scheduler = CUnicode('', config=True) | |
|
637 |
job_cmd = |
|
|
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 = |
|
|
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. |
|
|
716 |
# Add the |
|
|
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. |
|
|
744 | return os.path.join(self.profile_dir, self.job_file_name) | |
|
726 | 745 | |
|
727 |
def start(self, |
|
|
728 |
"""Start the controller by |
|
|
729 |
self.extra_args = [' |
|
|
730 |
self. |
|
|
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 = |
|
|
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. |
|
|
748 |
# Add the |
|
|
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. |
|
|
778 | return os.path.join(self.profile_dir, self.job_file_name) | |
|
758 | 779 | |
|
759 |
def start(self, n, |
|
|
760 |
"""Start the controller by |
|
|
761 |
self.extra_args = [' |
|
|
762 |
self. |
|
|
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 |
|
|
780 |
|
|
|
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 = |
|
|
804 |
job_array_template = |
|
|
824 | job_array_regexp = Unicode('') | |
|
825 | job_array_template = Unicode('') | |
|
805 | 826 | # PBS Queue regex |
|
806 |
queue_regexp = |
|
|
807 |
queue_template = |
|
|
827 | queue_regexp = Unicode('') | |
|
828 | queue_template = Unicode('') | |
|
808 | 829 | # The default batch template, override in subclasses |
|
809 |
default_template = |
|
|
830 | default_template = Unicode('') | |
|
810 | 831 | # The full path to the instantiated batch script. |
|
811 |
batch_file = |
|
|
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 = |
|
|
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, |
|
|
893 | def start(self, n, profile_dir): | |
|
872 | 894 | """Start n copies of the process using a batch system.""" |
|
873 |
# Here we save profile |
|
|
874 |
# can be used in the batch script template as |
|
|
875 | # ${cluster_dir} | |
|
876 |
self. |
|
|
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 = |
|
|
899 |
job_array_regexp = |
|
|
900 |
job_array_template = |
|
|
901 |
queue_regexp = |
|
|
902 |
queue_template = |
|
|
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 = |
|
|
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, |
|
|
916 |
"""Start the controller by profile or |
|
|
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, |
|
|
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 = |
|
|
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, |
|
|
931 |
"""Start n engines by profile or |
|
|
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, |
|
|
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 = |
|
|
940 |
job_array_template = |
|
|
941 |
queue_regexp = |
|
|
942 |
queue_template = |
|
|
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 = |
|
|
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, |
|
|
955 |
"""Start the controller by profile or |
|
|
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( |
|
|
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 = |
|
|
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, |
|
|
969 |
"""Start n engines by profile or |
|
|
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, |
|
|
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', ' |
|
|
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 |
[' |
|
|
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(Logging |
|
|
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 |
f |
|
|
66 |
self.log.debug("Subscribing to: |
|
|
67 |
self.stream.setsockopt(zmq.SUBSCRIBE, |
|
|
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 |
log |
|
|
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 |
""" |
|
|
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 |
""" |
|
|
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-20 |
|
|
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 |
|
|
|
28 |
Enum, Bool |
|
|
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 = |
|
|
78 |
job_name = |
|
|
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 = |
|
|
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 = |
|
|
91 |
job_type = |
|
|
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 = |
|
|
95 |
project = |
|
|
96 |
xmlns = |
|
|
97 |
version = |
|
|
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 = |
|
|
169 |
task_name = |
|
|
170 |
version = |
|
|
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 = |
|
|
178 |
command_line = |
|
|
179 |
work_directory = |
|
|
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 = |
|
|
182 |
std_err_file_path = |
|
|
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 = |
|
|
232 | job_name = Unicode('IPController', config=False) | |
|
227 | 233 | is_exclusive = Bool(False, config=True) |
|
228 |
username = |
|
|
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 = |
|
|
232 |
project = |
|
|
237 | requested_nodes = Unicode('', config=True) | |
|
238 | project = Unicode('IPython', config=True) | |
|
233 | 239 | |
|
234 | 240 | |
|
235 | 241 | class IPEngineSetJob(WinHPCJob): |
|
236 |
job_name = |
|
|
242 | job_name = Unicode('IPEngineSet', config=False) | |
|
237 | 243 | is_exclusive = Bool(False, config=True) |
|
238 |
username = |
|
|
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 = |
|
|
242 |
project = |
|
|
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 = |
|
|
253 | task_name = Unicode('IPController', config=True) | |
|
248 | 254 | controller_cmd = List(['ipcontroller.exe'], config=True) |
|
249 |
controller_args = List(['--log-to-file', ' |
|
|
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 = |
|
|
252 |
std_err_file_path = |
|
|
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 = |
|
|
260 |
work_directory = |
|
|
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 = |
|
|
281 | task_name = Unicode('IPEngine', config=True) | |
|
276 | 282 | engine_cmd = List(['ipengine.exe'], config=True) |
|
277 |
engine_args = List(['--log-to-file', ' |
|
|
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 = |
|
|
280 |
std_err_file_path = |
|
|
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 = |
|
|
288 |
work_directory = |
|
|
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, |
|
|
28 |
Dict, List, Bool, |
|
|
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. |
|
|
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 |
|
|
153 |
as cleartext, so if someone can snoop your |
|
|
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= |
|
|
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= |
|
|
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', |
|
|
238 |
context |
|
|
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_ |
|
|
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 |
|
|
|
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_ |
|
|
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 |
|
|
348 | if profile_dir is not None: | |
|
325 | 349 | try: |
|
326 |
self._cd = |
|
|
350 | self._cd = ProfileDir.find_profile_dir(profile_dir) | |
|
327 | 351 | return |
|
328 |
except |
|
|
352 | except ProfileDirError: | |
|
329 | 353 | pass |
|
330 | 354 | elif profile is not None: |
|
331 | 355 | try: |
|
332 |
self._cd = |
|
|
356 | self._cd = ProfileDir.find_profile_dir_by_name( | |
|
333 | 357 | ipython_dir, profile) |
|
334 | 358 | return |
|
335 |
except |
|
|
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 = |
|
|
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'] = |
|
|
505 | md['submitted'] = parent['date'] | |
|
482 | 506 | if 'started' in header: |
|
483 |
md['started'] = |
|
|
507 | md['started'] = header['started'] | |
|
484 | 508 | if 'date' in header: |
|
485 |
md['completed'] = |
|
|
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() |
|
|
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 = |
|
|
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, |
|
|
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 = |
|
|
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 |
|
|
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(Logging |
|
|
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 |
|
|
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 |
|
|
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': |
|
|
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 = |
|
|
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 = |
|
|
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 = In |
|
|
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 = In |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
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, |
|
|
230 |
p |
|
|
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, |
|
|
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' : ( |
|
|
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 |
log |
|
|
298 | log=self.log) | |
|
282 | 299 | |
|
283 | 300 | |
|
284 |
class Hub( |
|
|
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: S |
|
|
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: % |
|
|
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 |
|
|
554 | except: | |
|
555 |
self.log.error("queue::client %r sent invalid message to %r: % |
|
|
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: % |
|
|
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: % |
|
|
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 |
|
|
595 | except: | |
|
596 |
self.log.error("queue::engine %r sent invalid message to %r: % |
|
|
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 % |
|
|
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 = |
|
|
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 |
|
|
648 | except: | |
|
649 |
self.log.error("task::client %r sent invalid task message: % |
|
|
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 |
|
|
692 | except: | |
|
693 |
self.log.error("task::invalid task result message send to %r: % |
|
|
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 = |
|
|
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 % |
|
|
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 % |
|
|
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 % |
|
|
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: % |
|
|
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 % |
|
|
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: % |
|
|
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: % |
|
|
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(% |
|
|
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): % |
|
|
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'] = |
|
|
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, |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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. |
|
|
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 |
|
|
|
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: % |
|
|
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 |
|
|
|
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 |
|
|
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 |
|
|
|
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: % |
|
|
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) |
|
|
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, |
|
|
625 |
|
|
|
626 |
|
|
|
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 |
# |
|
|
651 |
if log_ |
|
|
652 |
connect_logger(logname, ctx, log_ |
|
|
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 |
|
|
|
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 |
|
|
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 = |
|
|
87 | location = CUnicode('', config=True) | |
|
88 |
|
|
|
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 |
|
|
225 | submitted timestamp, | |
|
161 | 226 | client_uuid text, |
|
162 | 227 | engine_uuid text, |
|
163 |
started |
|
|
164 |
completed |
|
|
165 |
resubmitted |
|
|
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, |
|
|
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, log |
|
|
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. |
|
|
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 = |
|
|
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, |
|
|
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 |
|
|
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 S |
|
|
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 |
|
|
|
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() |
|
|
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() |
|
|
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) 20 |
|
|
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. |
|
|
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 |
[' |
|
|
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(), ' |
|
|
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 + [' |
|
|
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 |
|
|
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 = |
|
|
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 |
|
|
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 |
|
|
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 |
' |
|
|
162 |
' |
|
|
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. |
|
|
9 | # Verify zmq version dependency >= 2.1.4 | |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 |
minimum_pyzmq_version = "2. |
|
|
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 |
|
|
|
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 Blocking |
|
|
64 | class BlockingShellSocketChannel(ShellSocketChannel): | |
|
65 | 65 | |
|
66 | 66 | def __init__(self, context, session, address=None): |
|
67 |
super(Blocking |
|
|
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('[[ |
|
|
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 Blocking |
|
|
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 |
|
|
|
117 | shell_channel_class = Type(BlockingShellSocketChannel) | |
|
118 | 118 | sub_channel_class = Type(BlockingSubSocketChannel) |
|
119 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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, |
|
|
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( |
|
|
208 |
int( |
|
|
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 |
|
|
218 |
|
|
|
219 | if pub_port <= 0: | |
|
220 | pub_port = ports.pop(0) | |
|
221 |
if |
|
|
222 |
|
|
|
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, ' |
|
|
230 |
' |
|
|
231 |
' |
|
|
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 += [ ' |
|
|
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 + [' |
|
|
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 + [' |
|
|
157 | proc = Popen(arguments + ['parent=1'], | |
|
297 | 158 | stdin=stdin, stdout=stdout, stderr=stderr) |
|
298 | 159 | |
|
299 |
return proc, |
|
|
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 |
|
|
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 |
|
|
|
75 | pub_socket = Instance('zmq.Socket') | |
|
76 |
|
|
|
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 = |
|
|
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. |
|
|
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 |
log |
|
|
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 |
log |
|
|
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, |
|
|
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 |
log |
|
|
213 |
log |
|
|
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. |
|
|
291 | reply_msg = self.session.send(self.shell_socket, u'execute_reply', | |
|
302 | 292 | reply_content, parent, ident=ident) |
|
303 |
log |
|
|
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. |
|
|
309 | completion_msg = self.session.send(self.shell_socket, 'complete_reply', | |
|
320 | 310 | matches, parent, ident) |
|
321 |
log |
|
|
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. |
|
|
317 | msg = self.session.send(self.shell_socket, 'object_info_reply', | |
|
328 | 318 | oinfo, parent, ident) |
|
329 |
log |
|
|
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. |
|
|
346 | msg = self.session.send(self.shell_socket, 'history_reply', | |
|
357 | 347 | content, parent, ident) |
|
358 |
log |
|
|
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. |
|
|
355 | msg = self.session.send(self.shell_socket, 'connect_reply', | |
|
366 | 356 | content, parent, ident) |
|
367 |
log |
|
|
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. |
|
|
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 |
log |
|
|
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. |
|
|
380 | reply_msg = self.session.send(self.shell_socket, reply_type, | |
|
391 | 381 | {'status' : 'aborted'}, msg, ident=ident) |
|
392 |
log |
|
|
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. |
|
|
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. |
|
|
397 | ident, reply = self.session.recv(self.stdin_socket, 0) | |
|
408 | 398 | try: |
|
409 | 399 | value = reply['content']['value'] |
|
410 | 400 | except: |
|
411 |
log |
|
|
412 |
log |
|
|
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. |
|
|
465 | self.session.send(self.pub_socket, self._shutdown_message) | |
|
466 |
log |
|
|
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, |
|
|
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 Z |
|
|
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(Z |
|
|
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 |
|
|
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( |
|
|
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( |
|
|
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(Z |
|
|
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 |
|
|
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( |
|
|
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( |
|
|
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(Z |
|
|
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 |
|
|
|
695 | shell_address = TCPAddress((LOCALHOST, 0)) | |
|
690 | 696 | sub_address = TCPAddress((LOCALHOST, 0)) |
|
691 |
|
|
|
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 |
|
|
|
701 | shell_channel_class = Type(ShellSocketChannel) | |
|
696 | 702 | sub_channel_class = Type(SubSocketChannel) |
|
697 |
|
|
|
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 |
_ |
|
|
708 | _shell_channel = Any | |
|
703 | 709 | _sub_channel = Any |
|
704 |
_ |
|
|
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. |
|
|
718 | # atexit.register(self.context.term) | |
|
711 | 719 | |
|
712 | 720 | #-------------------------------------------------------------------------- |
|
713 | 721 | # Channel management methods: |
|
714 | 722 | #-------------------------------------------------------------------------- |
|
715 | 723 | |
|
716 |
def start_channels(self, |
|
|
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 |
|
|
725 |
self. |
|
|
732 | if shell: | |
|
733 | self.shell_channel.start() | |
|
726 | 734 | if sub: |
|
727 | 735 | self.sub_channel.start() |
|
728 |
if |
|
|
729 |
self. |
|
|
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. |
|
|
737 |
self. |
|
|
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. |
|
|
741 |
self. |
|
|
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. |
|
|
749 |
self. |
|
|
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 |
|
|
|
770 |
self. |
|
|
771 |
if |
|
|
772 |
|
|
|
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 |
|
|
|
786 |
|
|
|
787 |
self. |
|
|
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. |
|
|
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. |
|
|
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 |
|
|
942 | def shell_channel(self): | |
|
935 | 943 | """Get the REQ socket channel object to make requests of the kernel.""" |
|
936 |
if self._ |
|
|
937 |
self._ |
|
|
944 | if self._shell_channel is None: | |
|
945 | self._shell_channel = self.shell_channel_class(self.context, | |
|
938 | 946 | self.session, |
|
939 |
self. |
|
|
940 |
return self._ |
|
|
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 |
|
|
960 | def stdin_channel(self): | |
|
953 | 961 | """Get the REP socket channel object to handle stdin (raw_input).""" |
|
954 |
if self._ |
|
|
955 |
self._ |
|
|
962 | if self._stdin_channel is None: | |
|
963 | self._stdin_channel = self.stdin_channel_class(self.context, | |
|
956 | 964 | self.session, |
|
957 |
self. |
|
|
958 |
return self._ |
|
|
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 |
|
|
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 = |
|
|
53 | _recorded_ports = Dict() | |
|
53 | 54 | |
|
54 | 55 | #--------------------------------------------------------------------------- |
|
55 | 56 | # Kernel interface |
|
56 | 57 | #--------------------------------------------------------------------------- |
|
57 | 58 | |
|
58 | 59 | session = Instance(Session) |
|
59 |
|
|
|
60 | pub_socket = Instance('zmq.Socket') | |
|
61 |
|
|
|
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. |
|
|
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 |
|
|
|
89 | self.log.error("UNKNOWN MESSAGE TYPE: %s"%omsg) | |
|
89 | 90 | else: |
|
90 | 91 | handler(ident, omsg) |
|
91 | 92 | |
|
92 |
def record_ports(self, |
|
|
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. |
|
|
157 |
|
|
|
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. |
|
|
159 | completion_msg = self.session.send(self.shell_socket, 'complete_reply', | |
|
165 | 160 | matches, parent, ident) |
|
166 |
|
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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, " |
|
|
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. |
|
|
201 |
|
|
|
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. |
|
|
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. |
|
|
211 | ident,reply = self.session.recv(self.stdin_socket, 0) | |
|
217 | 212 | try: |
|
218 | 213 | value = reply['content']['value'] |
|
219 | 214 | except: |
|
220 |
|
|
|
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_ |
|
|
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 |
|
|
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_ |
|
|
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_ |
|
|
54 |
"""Call show, close all open figures, sending all |
|
|
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_ |
|
|
65 |
"""Draw the current figure and send it as a |
|
|
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_ |
|
|
113 | 'IPython.zmq.pylab.backend_inline.send_figure', | |
|
78 | 114 | 'Matplotlib Plot', |
|
79 |
{ |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
s |
|
|
136 |
|
|
|
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 |
|
|
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_ |
|
|
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. |
|
|
39 |
KM. |
|
|
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. |
|
|
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. |
|
|
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 S |
|
|
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. |
|
|
51 | s = ss.Session() | |
|
52 | 52 | self.assertEquals(s.username, os.environ.get('USER', 'username')) |
|
53 | 53 | |
|
54 |
self.assertRaises(TypeError, ss. |
|
|
55 |
self.assertRaises(TypeError, ss. |
|
|
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. |
|
|
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 |
|
|
|
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. |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
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.i |
|
|
60 |
can put your extensions anywhere they can be imported but we |
|
|
61 |
:file:`extensions` subdirectory of the ipython directory to |
|
|
62 |
during extension loading, so you can put them there as well. |
|
|
63 |
are not executed in the user's interactive namespace and they |
|
|
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. |
|
|
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. |
|
|
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.i |
|
|
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. |
|
|
109 |
c. |
|
|
110 |
c. |
|
|
119 | c.IPythonTerminalApp.display_banner = True | |
|
120 | c.InteractiveShellApp.log_level = 20 | |
|
121 | c.InteractiveShellApp.extensions = [ | |
|
111 | 122 | 'myextension' |
|
112 | 123 | ] |
|
113 |
c. |
|
|
124 | c.InteractiveShellApp.exec_lines = [ | |
|
114 | 125 | 'import numpy', |
|
115 | 126 | 'import scipy' |
|
116 | 127 | ] |
|
117 |
c. |
|
|
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 |
|
|
22 |
IPython to their liking. Developer |
|
|
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.co |
|
|
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 |
|
|
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 = |
|
|
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? |
|
|
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 `` |
|
|
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 |
|
|
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. |
|
|
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 `` |
|
|
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 |
`` |
|
|
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 `` |
|
|
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, |
|
|
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 `` |
|
|
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 `` |
|
|
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 `` |
|
|
115 |
be found by pygments, and there are several already installed. The `` |
|
|
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 `` |
|
|
116 | styles associated with each ``colors`` option. | |
|
118 | 117 | |
|
119 | 118 | |
|
120 |
Screenshot of ``ipython-qtconsole |
|
|
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 `` |
|
|
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 |
- |
|
|
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 `` |
|
|
179 | listen on an external interface by specifying the ``ip`` argument:: | |
|
173 | 180 | |
|
174 |
$> ipython-qtconsole |
|
|
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 ipython |
|
|
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 |
|
|
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 |
|
|
|
68 | --pylab, pylab=<name> | |
|
69 | See :ref:`Matplotlib support <matplotlib_support>` | |
|
67 | 70 | for more details. |
|
68 | 71 | |
|
69 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
108 | --classic | |
|
106 | 109 | Gives IPython a similar feel to the classic Python |
|
107 | 110 | prompt. |
|
108 | 111 | |
|
109 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
257 | config_file=<name> | |
|
254 | 258 | name of your IPython resource configuration file. Normally |
|
255 |
IPython loads ipython |
|
|
256 |
IPYTHON_DIR/ |
|
|
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 |
|
|
|
288 | si=<string> | |
|
285 | 289 | |
|
286 | 290 | separator before input prompts. |
|
287 | 291 | Default: '\n' |
|
288 | 292 | |
|
289 |
|
|
|
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 |
|
|
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 |
|
|
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 |
- |
|
|
314 | ||
|
315 | -wxversion <string> | |
|
316 | Deprecated. | |
|
317 | --version print version information and exit. | |
|
317 | 318 | |
|
318 |
|
|
|
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 |
|
|
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 |
|
|
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 ` |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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:`mpi |
|
|
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/ |
|
|
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 |
|
|
83 | $ ipcluster start n=4 | |
|
84 | 84 | |
|
85 |
To see other command line options |
|
|
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 |
$ ip |
|
|
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 `` |
|
|
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), |
|
|
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 |
$ ip |
|
|
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. |
|
|
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 |
|
|
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. |
|
|
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 |
$ ip |
|
|
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= |
|
|
211 |
#PBS -q |
|
|
211 | #PBS -l nodes={n/4}:ppn=4 | |
|
212 | #PBS -q {queue} | |
|
212 | 213 | |
|
213 |
cd $ |
|
|
214 |
export PATH=$ |
|
|
215 |
export PYTHONPATH=$ |
|
|
216 |
/usr/local/bin/mpiexec -n |
|
|
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 : |
|
|
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 |
`` |
|
|
225 |
expressions like `` |
|
|
226 |
|
|
|
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 |
|
|
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 |
|
|
|
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 |
|
|
247 | #PBS -q {queue} | |
|
250 | 248 | |
|
251 |
cd $ |
|
|
252 |
export PATH=$ |
|
|
253 |
export PYTHONPATH=$ |
|
|
254 |
ipcontroller |
|
|
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 (`` |
|
|
271 |
submitted (`` |
|
|
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. |
|
|
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 |
|
|
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 |
$ ip |
|
|
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 |
|
|
338 |
# c.SSHControllerLauncher.program_args = ['- |
|
|
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, [' |
|
|
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 = [' |
|
|
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/ |
|
|
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/ |
|
|
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/ |
|
|
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/ |
|
|
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 |
Process |
|
|
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 * |
|
|
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, |
|
|
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 |
|
|
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/ |
|
|
134 |
|
|
|
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 |
|
|
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 `` |
|
|
346 |
argument to :command:`ipcontroller`, or in the :attr:` |
|
|
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 |
|
|
353 | $ ipcontroller scheme=<schemename> | |
|
354 | 354 | for instance: |
|
355 |
$ ipcontroller |
|
|
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 |
|
|
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: ``ip |
|
|
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 |
|
|
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:` |
|
|
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 |
ip |
|
|
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 |
|
|
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. |
|
|
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=[' |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
143 |
print_status('pyzmq', "no (require >= 2. |
|
|
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'] |
General Comments 0
You need to be logged in to leave comments.
Login now