##// END OF EJS Templates
Refactored the command line config system and other aspects of config....
Brian Granger -
Show More
@@ -1,386 +1,370 b''
1 1 # coding: utf-8
2 2 """A simple configuration system.
3 3
4 4 Authors
5 5 -------
6 6 * Brian Granger
7 7 * Fernando Perez
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import __builtin__
22 22 import os
23 23 import sys
24 24
25 25 from IPython.external import argparse
26 26 from IPython.utils.path import filefind
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Exceptions
30 30 #-----------------------------------------------------------------------------
31 31
32 32
33 33 class ConfigError(Exception):
34 34 pass
35 35
36 36
37 37 class ConfigLoaderError(ConfigError):
38 38 pass
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Argparse fix
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Unfortunately argparse by default prints help messages to stderr instead of
45 45 # stdout. This makes it annoying to capture long help screens at the command
46 46 # line, since one must know how to pipe stderr, which many users don't know how
47 47 # to do. So we override the print_help method with one that defaults to
48 48 # stdout and use our class instead.
49 49
50 50 class ArgumentParser(argparse.ArgumentParser):
51 51 """Simple argparse subclass that prints help to stdout by default."""
52 52
53 53 def print_help(self, file=None):
54 54 if file is None:
55 55 file = sys.stdout
56 56 return super(ArgumentParser, self).print_help(file)
57 57
58 58 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Config class for holding config information
62 62 #-----------------------------------------------------------------------------
63 63
64 64
65 65 class Config(dict):
66 66 """An attribute based dict that can do smart merges."""
67 67
68 68 def __init__(self, *args, **kwds):
69 69 dict.__init__(self, *args, **kwds)
70 70 # This sets self.__dict__ = self, but it has to be done this way
71 71 # because we are also overriding __setattr__.
72 72 dict.__setattr__(self, '__dict__', self)
73 73
74 74 def _merge(self, other):
75 75 to_update = {}
76 76 for k, v in other.items():
77 77 if not self.has_key(k):
78 78 to_update[k] = v
79 79 else: # I have this key
80 80 if isinstance(v, Config):
81 81 # Recursively merge common sub Configs
82 82 self[k]._merge(v)
83 83 else:
84 84 # Plain updates for non-Configs
85 85 to_update[k] = v
86 86
87 87 self.update(to_update)
88 88
89 89 def _is_section_key(self, key):
90 90 if key[0].upper()==key[0] and not key.startswith('_'):
91 91 return True
92 92 else:
93 93 return False
94 94
95 95 def has_key(self, key):
96 96 if self._is_section_key(key):
97 97 return True
98 98 else:
99 99 return dict.has_key(self, key)
100 100
101 101 def _has_section(self, key):
102 102 if self._is_section_key(key):
103 103 if dict.has_key(self, key):
104 104 return True
105 105 return False
106 106
107 107 def copy(self):
108 108 return type(self)(dict.copy(self))
109 109
110 110 def __copy__(self):
111 111 return self.copy()
112 112
113 113 def __deepcopy__(self, memo):
114 114 import copy
115 115 return type(self)(copy.deepcopy(self.items()))
116 116
117 117 def __getitem__(self, key):
118 118 # Because we use this for an exec namespace, we need to delegate
119 119 # the lookup of names in __builtin__ to itself. This means
120 120 # that you can't have section or attribute names that are
121 121 # builtins.
122 122 try:
123 123 return getattr(__builtin__, key)
124 124 except AttributeError:
125 125 pass
126 126 if self._is_section_key(key):
127 127 try:
128 128 return dict.__getitem__(self, key)
129 129 except KeyError:
130 130 c = Config()
131 131 dict.__setitem__(self, key, c)
132 132 return c
133 133 else:
134 134 return dict.__getitem__(self, key)
135 135
136 136 def __setitem__(self, key, value):
137 137 # Don't allow names in __builtin__ to be modified.
138 138 if hasattr(__builtin__, key):
139 139 raise ConfigError('Config variable names cannot have the same name '
140 140 'as a Python builtin: %s' % key)
141 141 if self._is_section_key(key):
142 142 if not isinstance(value, Config):
143 143 raise ValueError('values whose keys begin with an uppercase '
144 144 'char must be Config instances: %r, %r' % (key, value))
145 145 else:
146 146 dict.__setitem__(self, key, value)
147 147
148 148 def __getattr__(self, key):
149 149 try:
150 150 return self.__getitem__(key)
151 151 except KeyError, e:
152 152 raise AttributeError(e)
153 153
154 154 def __setattr__(self, key, value):
155 155 try:
156 156 self.__setitem__(key, value)
157 157 except KeyError, e:
158 158 raise AttributeError(e)
159 159
160 160 def __delattr__(self, key):
161 161 try:
162 162 dict.__delitem__(self, key)
163 163 except KeyError, e:
164 164 raise AttributeError(e)
165 165
166 166
167 167 #-----------------------------------------------------------------------------
168 168 # Config loading classes
169 169 #-----------------------------------------------------------------------------
170 170
171 171
172 172 class ConfigLoader(object):
173 173 """A object for loading configurations from just about anywhere.
174 174
175 175 The resulting configuration is packaged as a :class:`Struct`.
176 176
177 177 Notes
178 178 -----
179 179 A :class:`ConfigLoader` does one thing: load a config from a source
180 180 (file, command line arguments) and returns the data as a :class:`Struct`.
181 181 There are lots of things that :class:`ConfigLoader` does not do. It does
182 182 not implement complex logic for finding config files. It does not handle
183 183 default values or merge multiple configs. These things need to be
184 184 handled elsewhere.
185 185 """
186 186
187 187 def __init__(self):
188 188 """A base class for config loaders.
189 189
190 190 Examples
191 191 --------
192 192
193 193 >>> cl = ConfigLoader()
194 194 >>> config = cl.load_config()
195 195 >>> config
196 196 {}
197 197 """
198 198 self.clear()
199 199
200 200 def clear(self):
201 201 self.config = Config()
202 202
203 203 def load_config(self):
204 204 """Load a config from somewhere, return a :class:`Config` instance.
205 205
206 206 Usually, this will cause self.config to be set and then returned.
207 207 However, in most cases, :meth:`ConfigLoader.clear` should be called
208 208 to erase any previous state.
209 209 """
210 210 self.clear()
211 211 return self.config
212 212
213 213
214 214 class FileConfigLoader(ConfigLoader):
215 215 """A base class for file based configurations.
216 216
217 217 As we add more file based config loaders, the common logic should go
218 218 here.
219 219 """
220 220 pass
221 221
222 222
223 223 class PyFileConfigLoader(FileConfigLoader):
224 224 """A config loader for pure python files.
225 225
226 226 This calls execfile on a plain python file and looks for attributes
227 227 that are all caps. These attribute are added to the config Struct.
228 228 """
229 229
230 230 def __init__(self, filename, path=None):
231 231 """Build a config loader for a filename and path.
232 232
233 233 Parameters
234 234 ----------
235 235 filename : str
236 236 The file name of the config file.
237 237 path : str, list, tuple
238 238 The path to search for the config file on, or a sequence of
239 239 paths to try in order.
240 240 """
241 241 super(PyFileConfigLoader, self).__init__()
242 242 self.filename = filename
243 243 self.path = path
244 244 self.full_filename = ''
245 245 self.data = None
246 246
247 247 def load_config(self):
248 248 """Load the config from a file and return it as a Struct."""
249 249 self.clear()
250 250 self._find_file()
251 251 self._read_file_as_dict()
252 252 self._convert_to_config()
253 253 return self.config
254 254
255 255 def _find_file(self):
256 256 """Try to find the file by searching the paths."""
257 257 self.full_filename = filefind(self.filename, self.path)
258 258
259 259 def _read_file_as_dict(self):
260 260 """Load the config file into self.config, with recursive loading."""
261 261 # This closure is made available in the namespace that is used
262 262 # to exec the config file. This allows users to call
263 263 # load_subconfig('myconfig.py') to load config files recursively.
264 264 # It needs to be a closure because it has references to self.path
265 265 # and self.config. The sub-config is loaded with the same path
266 266 # as the parent, but it uses an empty config which is then merged
267 267 # with the parents.
268 268 def load_subconfig(fname):
269 269 loader = PyFileConfigLoader(fname, self.path)
270 270 try:
271 271 sub_config = loader.load_config()
272 272 except IOError:
273 273 # Pass silently if the sub config is not there. This happens
274 274 # when a user us using a profile, but not the default config.
275 275 pass
276 276 else:
277 277 self.config._merge(sub_config)
278 278
279 279 # Again, this needs to be a closure and should be used in config
280 280 # files to get the config being loaded.
281 281 def get_config():
282 282 return self.config
283 283
284 284 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
285 285 execfile(self.full_filename, namespace)
286 286
287 287 def _convert_to_config(self):
288 288 if self.data is None:
289 289 ConfigLoaderError('self.data does not exist')
290 290
291 291
292 292 class CommandLineConfigLoader(ConfigLoader):
293 293 """A config loader for command line arguments.
294 294
295 295 As we add more command line based loaders, the common logic should go
296 296 here.
297 297 """
298 298
299 299
300 300 class ArgParseConfigLoader(CommandLineConfigLoader):
301 301
302 def __init__(self, argv=None, arguments=(), *parser_args, **parser_kw):
302 def __init__(self, argv=None, *parser_args, **parser_kw):
303 303 """Create a config loader for use with argparse.
304 304
305 305 Parameters
306 306 ----------
307 307
308 308 argv : optional, list
309 309 If given, used to read command-line arguments from, otherwise
310 310 sys.argv[1:] is used.
311 311
312 arguments : optional, tuple
313 A tuple of two element tuples each having the form (args, kwargs).
314 Each such pair is passed to parser.add_argument(*args, **kwargs)
315 in sequence to configure the parser.
316
317 312 parser_args : tuple
318 313 A tuple of positional arguments that will be passed to the
319 314 constructor of :class:`argparse.ArgumentParser`.
320 315
321 316 parser_kw : dict
322 317 A tuple of keyword arguments that will be passed to the
323 318 constructor of :class:`argparse.ArgumentParser`.
324 319 """
325 320 super(CommandLineConfigLoader, self).__init__()
326 321 if argv == None:
327 322 argv = sys.argv[1:]
328 323 self.argv = argv
329 self.arguments = arguments
330 324 self.parser_args = parser_args
331 325 kwargs = dict(argument_default=argparse.SUPPRESS)
332 326 kwargs.update(parser_kw)
333 327 self.parser_kw = kwargs
334 328
335 329 def load_config(self, args=None):
336 330 """Parse command line arguments and return as a Struct.
337 331
338 332 Parameters
339 333 ----------
340 334
341 335 args : optional, list
342 336 If given, a list with the structure of sys.argv[1:] to parse
343 337 arguments from. If not given, the instance's self.argv attribute
344 338 (given at construction time) is used."""
345 339 self.clear()
346 340 if args is None:
347 341 args = self.argv
348 342 self._create_parser()
349 343 self._parse_args(args)
350 344 self._convert_to_config()
351 345 return self.config
352 346
353 347 def get_extra_args(self):
354 348 if hasattr(self, 'extra_args'):
355 349 return self.extra_args
356 350 else:
357 351 return []
358 352
359 353 def _create_parser(self):
360 354 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
361 355 self._add_arguments()
362 self._add_other_arguments()
363 356
364 357 def _add_arguments(self):
365 for argument in self.arguments:
366 # Remove any defaults in case people add them. We can't have
367 # command line default because all default are determined by
368 # traited class attributes.
369 argument[1].pop('default', None)
370 self.parser.add_argument(*argument[0], **argument[1])
371
372 def _add_other_arguments(self):
373 """Meant for subclasses to add their own arguments."""
374 pass
358 raise NotImplementedError("subclasses must implement _add_arguments")
375 359
376 360 def _parse_args(self, args):
377 361 """self.parser->self.parsed_data"""
378 362 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
379 363
380 364 def _convert_to_config(self):
381 365 """self.parsed_data->self.config"""
382 366 for k, v in vars(self.parsed_data).items():
383 367 exec_str = 'self.config.' + k + '= v'
384 368 exec exec_str in locals(), globals()
385 369
386 370
@@ -1,178 +1,174 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.config.loader
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2009 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 from tempfile import mkstemp
25 25 from unittest import TestCase
26 26
27 27 from IPython.config.loader import (
28 28 Config,
29 29 PyFileConfigLoader,
30 30 ArgParseConfigLoader,
31 31 ConfigError
32 32 )
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Actual tests
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 pyfile = """
40 40 c = get_config()
41 41 c.a = 10
42 42 c.b = 20
43 43 c.Foo.Bar.value = 10
44 44 c.Foo.Bam.value = range(10)
45 45 c.D.C.value = 'hi there'
46 46 """
47 47
48 48 class TestPyFileCL(TestCase):
49 49
50 50 def test_basic(self):
51 51 fd, fname = mkstemp('.py')
52 52 f = os.fdopen(fd, 'w')
53 53 f.write(pyfile)
54 54 f.close()
55 55 # Unlink the file
56 56 cl = PyFileConfigLoader(fname)
57 57 config = cl.load_config()
58 58 self.assertEquals(config.a, 10)
59 59 self.assertEquals(config.b, 20)
60 60 self.assertEquals(config.Foo.Bar.value, 10)
61 61 self.assertEquals(config.Foo.Bam.value, range(10))
62 62 self.assertEquals(config.D.C.value, 'hi there')
63 63
64
65 arguments = (
66 (('-f','--foo'), dict(dest='Global.foo', type=str)),
67 (('-b',), dict(dest='MyClass.bar', type=int)),
68 (('-n',), dict(dest='n', action='store_true')),
69 (('Global.bam',), dict(type=str))
70 )
64 class MyLoader1(ArgParseConfigLoader):
65 def _add_arguments(self):
66 p = self.parser
67 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
68 p.add_argument('-b', dest='MyClass.bar', type=int)
69 p.add_argument('-n', dest='n', action='store_true')
70 p.add_argument('Global.bam', type=str)
71
72 class MyLoader2(ArgParseConfigLoader):
73 def _add_arguments(self):
74 subparsers = self.parser.add_subparsers(dest='subparser_name')
75 subparser1 = subparsers.add_parser('1')
76 subparser1.add_argument('-x',dest='Global.x')
77 subparser2 = subparsers.add_parser('2')
78 subparser2.add_argument('y')
71 79
72 80 class TestArgParseCL(TestCase):
73 81
74 82 def test_basic(self):
75 cl = ArgParseConfigLoader(arguments=arguments)
83 cl = MyLoader1()
76 84 config = cl.load_config('-f hi -b 10 -n wow'.split())
77 85 self.assertEquals(config.Global.foo, 'hi')
78 86 self.assertEquals(config.MyClass.bar, 10)
79 87 self.assertEquals(config.n, True)
80 88 self.assertEquals(config.Global.bam, 'wow')
81 89 config = cl.load_config(['wow'])
82 90 self.assertEquals(config.keys(), ['Global'])
83 91 self.assertEquals(config.Global.keys(), ['bam'])
84 92 self.assertEquals(config.Global.bam, 'wow')
85 93
86 94 def test_add_arguments(self):
87
88 class MyLoader(ArgParseConfigLoader):
89 def _add_arguments(self):
90 subparsers = self.parser.add_subparsers(dest='subparser_name')
91 subparser1 = subparsers.add_parser('1')
92 subparser1.add_argument('-x',dest='Global.x')
93 subparser2 = subparsers.add_parser('2')
94 subparser2.add_argument('y')
95
96 cl = MyLoader()
95 cl = MyLoader2()
97 96 config = cl.load_config('2 frobble'.split())
98 97 self.assertEquals(config.subparser_name, '2')
99 98 self.assertEquals(config.y, 'frobble')
100 99 config = cl.load_config('1 -x frobble'.split())
101 100 self.assertEquals(config.subparser_name, '1')
102 101 self.assertEquals(config.Global.x, 'frobble')
103 102
104 103 def test_argv(self):
105 cl = ArgParseConfigLoader(
106 argv='-f hi -b 10 -n wow'.split(),
107 arguments=arguments
108 )
104 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
109 105 config = cl.load_config()
110 106 self.assertEquals(config.Global.foo, 'hi')
111 107 self.assertEquals(config.MyClass.bar, 10)
112 108 self.assertEquals(config.n, True)
113 109 self.assertEquals(config.Global.bam, 'wow')
114 110
115 111
116 112 class TestConfig(TestCase):
117 113
118 114 def test_setget(self):
119 115 c = Config()
120 116 c.a = 10
121 117 self.assertEquals(c.a, 10)
122 118 self.assertEquals(c.has_key('b'), False)
123 119
124 120 def test_auto_section(self):
125 121 c = Config()
126 122 self.assertEquals(c.has_key('A'), True)
127 123 self.assertEquals(c._has_section('A'), False)
128 124 A = c.A
129 125 A.foo = 'hi there'
130 126 self.assertEquals(c._has_section('A'), True)
131 127 self.assertEquals(c.A.foo, 'hi there')
132 128 del c.A
133 129 self.assertEquals(len(c.A.keys()),0)
134 130
135 131 def test_merge_doesnt_exist(self):
136 132 c1 = Config()
137 133 c2 = Config()
138 134 c2.bar = 10
139 135 c2.Foo.bar = 10
140 136 c1._merge(c2)
141 137 self.assertEquals(c1.Foo.bar, 10)
142 138 self.assertEquals(c1.bar, 10)
143 139 c2.Bar.bar = 10
144 140 c1._merge(c2)
145 141 self.assertEquals(c1.Bar.bar, 10)
146 142
147 143 def test_merge_exists(self):
148 144 c1 = Config()
149 145 c2 = Config()
150 146 c1.Foo.bar = 10
151 147 c1.Foo.bam = 30
152 148 c2.Foo.bar = 20
153 149 c2.Foo.wow = 40
154 150 c1._merge(c2)
155 151 self.assertEquals(c1.Foo.bam, 30)
156 152 self.assertEquals(c1.Foo.bar, 20)
157 153 self.assertEquals(c1.Foo.wow, 40)
158 154 c2.Foo.Bam.bam = 10
159 155 c1._merge(c2)
160 156 self.assertEquals(c1.Foo.Bam.bam, 10)
161 157
162 158 def test_deepcopy(self):
163 159 c1 = Config()
164 160 c1.Foo.bar = 10
165 161 c1.Foo.bam = 30
166 162 c1.a = 'asdf'
167 163 c1.b = range(10)
168 164 import copy
169 165 c2 = copy.deepcopy(c1)
170 166 self.assertEquals(c1, c2)
171 167 self.assert_(c1 is not c2)
172 168 self.assert_(c1.Foo is not c2.Foo)
173 169
174 170 def test_builtin(self):
175 171 c1 = Config()
176 172 exec 'foo = True' in c1
177 173 self.assertEquals(c1.foo, True)
178 174 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
@@ -1,489 +1,463 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the components, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15
16 16 Notes
17 17 -----
18 18 """
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Copyright (C) 2008-2009 The IPython Development Team
22 22 #
23 23 # Distributed under the terms of the BSD License. The full license is in
24 24 # the file COPYING, distributed as part of this software.
25 25 #-----------------------------------------------------------------------------
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Imports
29 29 #-----------------------------------------------------------------------------
30 30
31 31 import logging
32 32 import os
33 33 import sys
34 34
35 35 from IPython.core import release, crashhandler
36 36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 37 from IPython.config.loader import (
38 38 PyFileConfigLoader,
39 39 ArgParseConfigLoader,
40 40 Config,
41 41 )
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Classes and functions
45 45 #-----------------------------------------------------------------------------
46 46
47 47 class ApplicationError(Exception):
48 48 pass
49 49
50 50
51 app_cl_args = (
52 (('--ipython-dir', ), dict(
53 dest='Global.ipython_dir',type=unicode,
54 help=
55 """Set to override default location of the IPython directory
56 IPYTHON_DIR, stored as Global.ipython_dir. This can also be specified
57 through the environment variable IPYTHON_DIR.""",
58 metavar='Global.ipython_dir') ),
59 (('-p', '--profile',), dict(
60 dest='Global.profile',type=unicode,
61 help=
62 """The string name of the ipython profile to be used. Assume that your
63 config file is ipython_config-<name>.py (looks in current dir first,
64 then in IPYTHON_DIR). This is a quick way to keep and load multiple
65 config files for different tasks, especially if include your basic one
66 in your more specialized ones. You can keep a basic
67 IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
68 include this one and load extra things for particular tasks.""",
69 metavar='Global.profile') ),
70 (('--log-level',), dict(
71 dest="Global.log_level",type=int,
72 help='Set the log level (0,10,20,30,40,50). Default is 30.',
73 metavar='Global.log_level')),
74 (('--config-file',), dict(
75 dest='Global.config_file',type=unicode,
76 help=
77 """Set the config file name to override default. Normally IPython
78 loads ipython_config.py (from current directory) or
79 IPYTHON_DIR/ipython_config.py. If the loading of your config file
80 fails, IPython starts with a bare bones configuration (no modules
81 loaded at all).""",
82 metavar='Global.config_file')),
83 )
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_arguments(self):
74 self._add_ipython_dir(self.parser)
75 self._add_log_level(self.parser)
76
84 77
85 78 class Application(object):
86 79 """Load a config, construct components and set them running.
87 80
88 The configuration of an application can be done via four different Config
89 objects, which are loaded and ultimately merged into a single one used from
90 that point on by the app. These are:
81 The configuration of an application can be done via three different Config
82 objects, which are loaded and ultimately merged into a single one used
83 from that point on by the app. These are:
91 84
92 85 1. default_config: internal defaults, implemented in code.
93 86 2. file_config: read from the filesystem.
94 87 3. command_line_config: read from the system's command line flags.
95 4. constructor_config: passed parametrically to the constructor.
96 88
97 89 During initialization, 3 is actually read before 2, since at the
98 90 command-line one may override the location of the file to be read. But the
99 91 above is the order in which the merge is made.
100
101 There is a final config object can be created and passed to the
102 constructor: override_config. If it exists, this completely overrides the
103 configs 2-4 above (the default is still used to ensure that all needed
104 fields at least are created). This makes it easier to create
105 parametrically (e.g. in testing or sphinx plugins) objects with a known
106 configuration, that are unaffected by whatever arguments may be present in
107 sys.argv or files in the user's various directories.
108 92 """
109 93
110 94 name = u'ipython'
111 95 description = 'IPython: an enhanced interactive Python shell.'
112 #: usage message printed by argparse. If None, auto-generate
96 #: Usage message printed by argparse. If None, auto-generate
113 97 usage = None
98 #: The command line config loader. Subclass of ArgParseConfigLoader.
99 command_line_loader = BaseAppConfigLoader
100 #: The name of the config file to load.
114 101 config_file_name = u'ipython_config.py'
115 #: Track the default and actual separately because some messages are
116 #: only printed if we aren't using the default.
102 #: The name of the default config file. Track separately from the actual
103 #: name because some logic happens only if we aren't using the default.
117 104 default_config_file_name = config_file_name
118 105 default_log_level = logging.WARN
119 106 #: Set by --profile option
120 107 profile_name = None
121 108 #: User's ipython directory, typically ~/.ipython/
122 109 ipython_dir = None
123 #: internal defaults, implemented in code.
110 #: Internal defaults, implemented in code.
124 111 default_config = None
125 #: read from the filesystem
112 #: Read from the filesystem.
126 113 file_config = None
127 #: read from the system's command line flags
114 #: Read from the system's command line flags.
128 115 command_line_config = None
129 #: passed parametrically to the constructor.
130 constructor_config = None
131 #: final override, if given supercedes file/command/constructor configs
132 override_config = None
116 #: The final config that will be passed to the component.
117 master_config = None
133 118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
134 119 argv = None
135 #: Default command line arguments. Subclasses should create a new tuple
136 #: that *includes* these.
137 cl_arguments = app_cl_args
138
139 120 #: extra arguments computed by the command-line loader
140 121 extra_args = None
141 122
142 123 # Private attributes
143 124 _exiting = False
144 125 _initialized = False
145 126
146 127 # Class choices for things that will be instantiated at runtime.
147 128 _CrashHandler = crashhandler.CrashHandler
148 129
149 def __init__(self, argv=None, constructor_config=None, override_config=None):
130 def __init__(self, argv=None):
150 131 self.argv = sys.argv[1:] if argv is None else argv
151 self.constructor_config = constructor_config
152 self.override_config = override_config
153 132 self.init_logger()
154 133
155 134 def init_logger(self):
156 135 self.log = logging.getLogger(self.__class__.__name__)
157 136 # This is used as the default until the command line arguments are read.
158 137 self.log.setLevel(self.default_log_level)
159 138 self._log_handler = logging.StreamHandler()
160 139 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
161 140 self._log_handler.setFormatter(self._log_formatter)
162 141 self.log.addHandler(self._log_handler)
163 142
164 143 def _set_log_level(self, level):
165 144 self.log.setLevel(level)
166 145
167 146 def _get_log_level(self):
168 147 return self.log.level
169 148
170 149 log_level = property(_get_log_level, _set_log_level)
171 150
172 151 def initialize(self):
173 152 """Initialize the application.
174 153
175 154 Loads all configuration information and sets all application state, but
176 155 does not start any relevant processing (typically some kind of event
177 156 loop).
178 157
179 158 Once this method has been called, the application is flagged as
180 159 initialized and the method becomes a no-op."""
181 160
182 161 if self._initialized:
183 162 return
184 163
185 164 # The first part is protected with an 'attempt' wrapper, that will log
186 165 # failures with the basic system traceback machinery. Once our crash
187 166 # handler is in place, we can let any subsequent exception propagate,
188 167 # as our handler will log it with much better detail than the default.
189 168 self.attempt(self.create_crash_handler)
190 169
191 170 # Configuration phase
192 171 # Default config (internally hardwired in application code)
193 172 self.create_default_config()
194 173 self.log_default_config()
195 174 self.set_default_config_log_level()
196 175
197 if self.override_config is None:
198 # Command-line config
199 self.pre_load_command_line_config()
200 self.load_command_line_config()
201 self.set_command_line_config_log_level()
202 self.post_load_command_line_config()
203 self.log_command_line_config()
176 # Command-line config
177 self.pre_load_command_line_config()
178 self.load_command_line_config()
179 self.set_command_line_config_log_level()
180 self.post_load_command_line_config()
181 self.log_command_line_config()
204 182
205 183 # Find resources needed for filesystem access, using information from
206 184 # the above two
207 185 self.find_ipython_dir()
208 186 self.find_resources()
209 187 self.find_config_file_name()
210 188 self.find_config_file_paths()
211 189
212 if self.override_config is None:
213 # File-based config
214 self.pre_load_file_config()
215 self.load_file_config()
216 self.set_file_config_log_level()
217 self.post_load_file_config()
218 self.log_file_config()
190 # File-based config
191 self.pre_load_file_config()
192 self.load_file_config()
193 self.set_file_config_log_level()
194 self.post_load_file_config()
195 self.log_file_config()
219 196
220 197 # Merge all config objects into a single one the app can then use
221 198 self.merge_configs()
222 199 self.log_master_config()
223 200
224 201 # Construction phase
225 202 self.pre_construct()
226 203 self.construct()
227 204 self.post_construct()
228 205
229 206 # Done, flag as such and
230 207 self._initialized = True
231 208
232 209 def start(self):
233 210 """Start the application."""
234 211 self.initialize()
235 212 self.start_app()
236 213
237 214 #-------------------------------------------------------------------------
238 215 # Various stages of Application creation
239 216 #-------------------------------------------------------------------------
240 217
241 218 def create_crash_handler(self):
242 219 """Create a crash handler, typically setting sys.excepthook to it."""
243 220 self.crash_handler = self._CrashHandler(self, self.name)
244 221 sys.excepthook = self.crash_handler
245 222
246 223 def create_default_config(self):
247 224 """Create defaults that can't be set elsewhere.
248 225
249 226 For the most part, we try to set default in the class attributes
250 227 of Components. But, defaults the top-level Application (which is
251 228 not a HasTraitlets or Component) are not set in this way. Instead
252 229 we set them here. The Global section is for variables like this that
253 230 don't belong to a particular component.
254 231 """
255 232 c = Config()
256 233 c.Global.ipython_dir = get_ipython_dir()
257 234 c.Global.log_level = self.log_level
258 235 self.default_config = c
259 236
260 237 def log_default_config(self):
261 238 self.log.debug('Default config loaded:')
262 239 self.log.debug(repr(self.default_config))
263 240
264 241 def set_default_config_log_level(self):
265 242 try:
266 243 self.log_level = self.default_config.Global.log_level
267 244 except AttributeError:
268 245 # Fallback to the default_log_level class attribute
269 246 pass
270 247
271 248 def create_command_line_config(self):
272 249 """Create and return a command line config loader."""
273 return ArgParseConfigLoader(self.argv, self.cl_arguments,
274 description=self.description,
275 version=release.version,
276 usage=self.usage,
277 )
250 return self.command_line_loader(
251 self.argv,
252 description=self.description,
253 version=release.version,
254 usage=self.usage
255 )
278 256
279 257 def pre_load_command_line_config(self):
280 258 """Do actions just before loading the command line config."""
281 259 pass
282 260
283 261 def load_command_line_config(self):
284 262 """Load the command line config."""
285 263 loader = self.create_command_line_config()
286 264 self.command_line_config = loader.load_config()
287 265 self.extra_args = loader.get_extra_args()
288 266
289 267 def set_command_line_config_log_level(self):
290 268 try:
291 269 self.log_level = self.command_line_config.Global.log_level
292 270 except AttributeError:
293 271 pass
294 272
295 273 def post_load_command_line_config(self):
296 274 """Do actions just after loading the command line config."""
297 275 pass
298 276
299 277 def log_command_line_config(self):
300 278 self.log.debug("Command line config loaded:")
301 279 self.log.debug(repr(self.command_line_config))
302 280
303 281 def find_ipython_dir(self):
304 282 """Set the IPython directory.
305 283
306 284 This sets ``self.ipython_dir``, but the actual value that is passed to
307 285 the application is kept in either ``self.default_config`` or
308 286 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
309 287 ``sys.path`` so config files there can be referenced by other config
310 288 files.
311 289 """
312 290
313 291 try:
314 292 self.ipython_dir = self.command_line_config.Global.ipython_dir
315 293 except AttributeError:
316 294 self.ipython_dir = self.default_config.Global.ipython_dir
317 295 sys.path.append(os.path.abspath(self.ipython_dir))
318 296 if not os.path.isdir(self.ipython_dir):
319 297 os.makedirs(self.ipython_dir, mode=0777)
320 298 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
321 299
322 300 def find_resources(self):
323 301 """Find other resources that need to be in place.
324 302
325 303 Things like cluster directories need to be in place to find the
326 304 config file. These happen right after the IPython directory has
327 305 been set.
328 306 """
329 307 pass
330 308
331 309 def find_config_file_name(self):
332 310 """Find the config file name for this application.
333 311
334 312 This must set ``self.config_file_name`` to the filename of the
335 313 config file to use (just the filename). The search paths for the
336 314 config file are set in :meth:`find_config_file_paths` and then passed
337 315 to the config file loader where they are resolved to an absolute path.
338 316
339 317 If a profile has been set at the command line, this will resolve it.
340 318 """
341 319
342 320 try:
343 321 self.config_file_name = self.command_line_config.Global.config_file
344 322 except AttributeError:
345 323 pass
346 324
347 325 try:
348 326 self.profile_name = self.command_line_config.Global.profile
349 327 except AttributeError:
350 328 pass
351 329 else:
352 330 name_parts = self.config_file_name.split('.')
353 331 name_parts.insert(1, u'_' + self.profile_name + u'.')
354 332 self.config_file_name = ''.join(name_parts)
355 333
356 334 def find_config_file_paths(self):
357 335 """Set the search paths for resolving the config file.
358 336
359 337 This must set ``self.config_file_paths`` to a sequence of search
360 338 paths to pass to the config file loader.
361 339 """
362 340 # Include our own profiles directory last, so that users can still find
363 341 # our shipped copies of builtin profiles even if they don't have them
364 342 # in their local ipython directory.
365 343 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
366 344 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
367 345
368 346 def pre_load_file_config(self):
369 347 """Do actions before the config file is loaded."""
370 348 pass
371 349
372 350 def load_file_config(self):
373 351 """Load the config file.
374 352
375 353 This tries to load the config file from disk. If successful, the
376 354 ``CONFIG_FILE`` config variable is set to the resolved config file
377 355 location. If not successful, an empty config is used.
378 356 """
379 357 self.log.debug("Attempting to load config file: %s" %
380 358 self.config_file_name)
381 359 loader = PyFileConfigLoader(self.config_file_name,
382 360 path=self.config_file_paths)
383 361 try:
384 362 self.file_config = loader.load_config()
385 363 self.file_config.Global.config_file = loader.full_filename
386 364 except IOError:
387 365 # Only warn if the default config file was NOT being used.
388 366 if not self.config_file_name==self.default_config_file_name:
389 367 self.log.warn("Config file not found, skipping: %s" %
390 368 self.config_file_name, exc_info=True)
391 369 self.file_config = Config()
392 370 except:
393 371 self.log.warn("Error loading config file: %s" %
394 372 self.config_file_name, exc_info=True)
395 373 self.file_config = Config()
396 374
397 375 def set_file_config_log_level(self):
398 376 # We need to keeep self.log_level updated. But we only use the value
399 377 # of the file_config if a value was not specified at the command
400 378 # line, because the command line overrides everything.
401 379 if not hasattr(self.command_line_config.Global, 'log_level'):
402 380 try:
403 381 self.log_level = self.file_config.Global.log_level
404 382 except AttributeError:
405 383 pass # Use existing value
406 384
407 385 def post_load_file_config(self):
408 386 """Do actions after the config file is loaded."""
409 387 pass
410 388
411 389 def log_file_config(self):
412 390 if hasattr(self.file_config.Global, 'config_file'):
413 391 self.log.debug("Config file loaded: %s" %
414 392 self.file_config.Global.config_file)
415 393 self.log.debug(repr(self.file_config))
416 394
417 395 def merge_configs(self):
418 396 """Merge the default, command line and file config objects."""
419 397 config = Config()
420 398 config._merge(self.default_config)
421 if self.override_config is None:
422 config._merge(self.file_config)
423 config._merge(self.command_line_config)
424 if self.constructor_config is not None:
425 config._merge(self.constructor_config)
426 else:
427 config._merge(self.override_config)
399 config._merge(self.file_config)
400 config._merge(self.command_line_config)
401
428 402 # XXX fperez - propose to Brian we rename master_config to simply
429 403 # config, I think this is going to be heavily used in examples and
430 404 # application code and the name is shorter/easier to find/remember.
431 405 # For now, just alias it...
432 406 self.master_config = config
433 407 self.config = config
434 408
435 409 def log_master_config(self):
436 410 self.log.debug("Master config created:")
437 411 self.log.debug(repr(self.master_config))
438 412
439 413 def pre_construct(self):
440 414 """Do actions after the config has been built, but before construct."""
441 415 pass
442 416
443 417 def construct(self):
444 418 """Construct the main components that make up this app."""
445 419 self.log.debug("Constructing components for application")
446 420
447 421 def post_construct(self):
448 422 """Do actions after construct, but before starting the app."""
449 423 pass
450 424
451 425 def start_app(self):
452 426 """Actually start the app."""
453 427 self.log.debug("Starting application")
454 428
455 429 #-------------------------------------------------------------------------
456 430 # Utility methods
457 431 #-------------------------------------------------------------------------
458 432
459 433 def abort(self):
460 434 """Abort the starting of the application."""
461 435 if self._exiting:
462 436 pass
463 437 else:
464 438 self.log.critical("Aborting application: %s" % self.name, exc_info=True)
465 439 self._exiting = True
466 440 sys.exit(1)
467 441
468 442 def exit(self, exit_status=0):
469 443 if self._exiting:
470 444 pass
471 445 else:
472 446 self.log.debug("Exiting application: %s" % self.name)
473 447 self._exiting = True
474 448 sys.exit(exit_status)
475 449
476 450 def attempt(self, func, action='abort'):
477 451 try:
478 452 func()
479 453 except SystemExit:
480 454 raise
481 455 except:
482 456 if action == 'abort':
483 457 self.log.critical("Aborting application: %s" % self.name,
484 458 exc_info=True)
485 459 self.abort()
486 460 raise
487 461 elif action == 'exit':
488 462 self.exit(0)
489 463
@@ -1,223 +1,224 b''
1 1 # -*- coding: utf-8 -*-
2 2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3 3
4 4
5 5 Authors
6 6 -------
7 7 - Fernando Perez <Fernando.Perez@berkeley.edu>
8 8 """
9 9
10 10 #*****************************************************************************
11 11 # Copyright (C) 2008-2009 The IPython Development Team
12 12 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #*****************************************************************************
17 17
18 18 #****************************************************************************
19 19 # Required modules
20 20
21 21 # From the standard library
22 22 import os
23 23 import sys
24 24 from pprint import pformat
25 25
26 26 # Our own
27 27 from IPython.core import release
28 28 from IPython.core import ultratb
29 29 from IPython.utils.sysinfo import sys_info
30 30
31 31 from IPython.external.Itpl import itpl
32 32
33 33 #****************************************************************************
34 34
35 35 class CrashHandler(object):
36 36 """Customizable crash handlers for IPython-based systems.
37 37
38 38 Instances of this class provide a __call__ method which can be used as a
39 39 sys.excepthook, i.e., the __call__ signature is:
40 40
41 41 def __call__(self,etype, evalue, etb)
42 42
43 43 """
44 44
45 45 def __init__(self,app, app_name, contact_name=None, contact_email=None,
46 46 bug_tracker=None, crash_report_fname='CrashReport.txt',
47 47 show_crash_traceback=True, call_pdb=False):
48 48 """New crash handler.
49 49
50 50 Inputs:
51 51
52 52 - app: a running application instance, which will be queried at crash
53 53 time for internal information.
54 54
55 55 - app_name: a string containing the name of your application.
56 56
57 57 - contact_name: a string with the name of the person to contact.
58 58
59 59 - contact_email: a string with the email address of the contact.
60 60
61 61 - bug_tracker: a string with the URL for your project's bug tracker.
62 62
63 63 - crash_report_fname: a string with the filename for the crash report
64 64 to be saved in. These reports are left in the ipython user directory
65 65 as determined by the running IPython instance.
66 66
67 67 Optional inputs:
68 68
69 69 - show_crash_traceback(True): if false, don't print the crash
70 70 traceback on stderr, only generate the on-disk report
71 71
72 72
73 73 Non-argument instance attributes:
74 74
75 75 These instances contain some non-argument attributes which allow for
76 76 further customization of the crash handler's behavior. Please see the
77 77 source for further details.
78 78 """
79 79
80 80 # apply args into instance
81 81 self.app = app
82 82 self.app_name = app_name
83 83 self.contact_name = contact_name
84 84 self.contact_email = contact_email
85 85 self.bug_tracker = bug_tracker
86 86 self.crash_report_fname = crash_report_fname
87 87 self.show_crash_traceback = show_crash_traceback
88 88 self.section_sep = '\n\n'+'*'*75+'\n\n'
89 89 self.call_pdb = call_pdb
90 90 #self.call_pdb = True # dbg
91 91
92 92 # Hardcoded defaults, which can be overridden either by subclasses or
93 93 # at runtime for the instance.
94 94
95 95 # Template for the user message. Subclasses which completely override
96 96 # this, or user apps, can modify it to suit their tastes. It gets
97 97 # expanded using itpl, so calls of the kind $self.foo are valid.
98 98 self.user_message_template = """
99 99 Oops, $self.app_name crashed. We do our best to make it stable, but...
100 100
101 101 A crash report was automatically generated with the following information:
102 102 - A verbatim copy of the crash traceback.
103 103 - A copy of your input history during this session.
104 104 - Data on your current $self.app_name configuration.
105 105
106 106 It was left in the file named:
107 107 \t'$self.crash_report_fname'
108 108 If you can email this file to the developers, the information in it will help
109 109 them in understanding and correcting the problem.
110 110
111 111 You can mail it to: $self.contact_name at $self.contact_email
112 112 with the subject '$self.app_name Crash Report'.
113 113
114 114 If you want to do it now, the following command will work (under Unix):
115 115 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
116 116
117 117 To ensure accurate tracking of this issue, please file a report about it at:
118 118 $self.bug_tracker
119 119 """
120 120
121 121 def __call__(self,etype, evalue, etb):
122 122 """Handle an exception, call for compatible with sys.excepthook"""
123 123
124 124 # Report tracebacks shouldn't use color in general (safer for users)
125 125 color_scheme = 'NoColor'
126 126
127 127 # Use this ONLY for developer debugging (keep commented out for release)
128 128 #color_scheme = 'Linux' # dbg
129 129
130 130 try:
131 131 rptdir = self.app.ipython_dir
132 132 except:
133 133 rptdir = os.getcwd()
134 134 if not os.path.isdir(rptdir):
135 135 rptdir = os.getcwd()
136 136 report_name = os.path.join(rptdir,self.crash_report_fname)
137 137 # write the report filename into the instance dict so it can get
138 138 # properly expanded out in the user message template
139 139 self.crash_report_fname = report_name
140 140 TBhandler = ultratb.VerboseTB(color_scheme=color_scheme,
141 141 long_header=1,
142 142 call_pdb=self.call_pdb,
143 143 )
144 144 if self.call_pdb:
145 145 TBhandler(etype,evalue,etb)
146 146 return
147 147 else:
148 148 traceback = TBhandler.text(etype,evalue,etb,context=31)
149 149
150 150 # print traceback to screen
151 151 if self.show_crash_traceback:
152 152 print >> sys.stderr, traceback
153 153
154 154 # and generate a complete report on disk
155 155 try:
156 156 report = open(report_name,'w')
157 157 except:
158 158 print >> sys.stderr, 'Could not create crash report on disk.'
159 159 return
160 160
161 161 # Inform user on stderr of what happened
162 162 msg = itpl('\n'+'*'*70+'\n'+self.user_message_template)
163 163 print >> sys.stderr, msg
164 164
165 165 # Construct report on disk
166 166 report.write(self.make_report(traceback))
167 167 report.close()
168 168 raw_input("Hit <Enter> to quit this message (your terminal may close):")
169 169
170 170 def make_report(self,traceback):
171 171 """Return a string containing a crash report."""
172 172
173 173 sec_sep = self.section_sep
174 174
175 175 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
176 176 rpt_add = report.append
177 177 rpt_add(sys_info())
178 178
179 179 try:
180 180 config = pformat(self.app.config)
181 181 rpt_add(sec_sep+'Current user configuration structure:\n\n')
182 182 rpt_add(config)
183 183 except:
184 184 pass
185 185 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
186 186
187 187 return ''.join(report)
188 188
189 189
190 190 class IPythonCrashHandler(CrashHandler):
191 191 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
192 192
193 193 def __init__(self, app, app_name='IPython'):
194 194
195 195 # Set here which of the IPython authors should be listed as contact
196 196 AUTHOR_CONTACT = 'Fernando'
197 197
198 198 # Set argument defaults
199 199 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
200 200 contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2]
201 201 crash_report_fname = 'IPython_crash_report.txt'
202 202 # Call parent constructor
203 203 CrashHandler.__init__(self,app,app_name,contact_name,contact_email,
204 204 bug_tracker,crash_report_fname)
205 205
206 206 def make_report(self,traceback):
207 207 """Return a string containing a crash report."""
208 208
209 209 sec_sep = self.section_sep
210 210 # Start with parent report
211 211 report = [super(IPythonCrashHandler, self).make_report(traceback)]
212 212 # Add interactive-specific info we may have
213 213 rpt_add = report.append
214 214 try:
215 215 rpt_add(sec_sep+"History of session input:")
216 216 for line in self.app.shell.user_ns['_ih']:
217 217 rpt_add(line)
218 218 rpt_add('\n*** Last line of input (may not be in above history):\n')
219 219 rpt_add(self.app.shell._last_input_line+'\n')
220 220 except:
221 221 pass
222 222
223 223 return ''.join(report)
224
This diff has been collapsed as it changes many lines, (608 lines changed) Show them Hide them
@@ -1,648 +1,590 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2010 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24 from __future__ import absolute_import
25 25
26 26 import logging
27 27 import os
28 28 import sys
29 29
30 30 from IPython.core import crashhandler
31 from IPython.core.application import Application
31 from IPython.core.application import Application, BaseAppConfigLoader
32 32 from IPython.core.iplib import InteractiveShell
33 33 from IPython.config.loader import (
34 34 Config,
35 35 PyFileConfigLoader
36 36 )
37 37 from IPython.lib import inputhook
38 38 from IPython.utils.path import filefind, get_ipython_dir
39 39 from . import usage
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Globals, utilities and helpers
43 43 #-----------------------------------------------------------------------------
44 44
45 #: The default config file name for this application.
45 46 default_config_file_name = u'ipython_config.py'
46 47
47 cl_args = (
48 (('--autocall',), dict(
49 type=int, dest='InteractiveShell.autocall',
50 help=
51 """Make IPython automatically call any callable object even if you
52 didn't type explicit parentheses. For example, 'str 43' becomes
53 'str(43)' automatically. The value can be '0' to disable the feature,
54 '1' for 'smart' autocall, where it is not applied if there are no more
55 arguments on the line, and '2' for 'full' autocall, where all callable
56 objects are automatically called (even if no arguments are present).
57 The default is '1'.""",
58 metavar='InteractiveShell.autocall')
59 ),
60 (('--autoindent',), dict(
61 action='store_true', dest='InteractiveShell.autoindent',
62 help='Turn on autoindenting.')
63 ),
64 (('--no-autoindent',), dict(
65 action='store_false', dest='InteractiveShell.autoindent',
66 help='Turn off autoindenting.')
67 ),
68 (('--automagic',), dict(
69 action='store_true', dest='InteractiveShell.automagic',
70 help='Turn on the auto calling of magic commands.'
71 'Type %%magic at the IPython prompt for more information.')
72 ),
73 (('--no-automagic',), dict(
74 action='store_false', dest='InteractiveShell.automagic',
75 help='Turn off the auto calling of magic commands.')
76 ),
77 (('--autoedit-syntax',), dict(
78 action='store_true', dest='InteractiveShell.autoedit_syntax',
79 help='Turn on auto editing of files with syntax errors.')
80 ),
81 (('--no-autoedit-syntax',), dict(
82 action='store_false', dest='InteractiveShell.autoedit_syntax',
83 help='Turn off auto editing of files with syntax errors.')
84 ),
85 (('--banner',), dict(
86 action='store_true', dest='Global.display_banner',
87 help='Display a banner upon starting IPython.')
88 ),
89 (('--no-banner',), dict(
90 action='store_false', dest='Global.display_banner',
91 help="Don't display a banner upon starting IPython.")
92 ),
93 (('--cache-size',), dict(
94 type=int, dest='InteractiveShell.cache_size',
95 help=
96 """Set the size of the output cache. The default is 1000, you can
97 change it permanently in your config file. Setting it to 0 completely
98 disables the caching system, and the minimum value accepted is 20 (if
99 you provide a value less than 20, it is reset to 0 and a warning is
100 issued). This limit is defined because otherwise you'll spend more
101 time re-flushing a too small cache than working.
102 """,
103 metavar='InteractiveShell.cache_size')
104 ),
105 (('--classic',), dict(
106 action='store_true', dest='Global.classic',
107 help="Gives IPython a similar feel to the classic Python prompt.")
108 ),
109 (('--colors',), dict(
110 type=str, dest='InteractiveShell.colors',
111 help="Set the color scheme (NoColor, Linux, and LightBG).",
112 metavar='InteractiveShell.colors')
113 ),
114 (('--color-info',), dict(
115 action='store_true', dest='InteractiveShell.color_info',
116 help=
117 """IPython can display information about objects via a set of func-
118 tions, and optionally can use colors for this, syntax highlighting
119 source code and various other elements. However, because this
120 information is passed through a pager (like 'less') and many pagers get
121 confused with color codes, this option is off by default. You can test
122 it and turn it on permanently in your ipython_config.py file if it
123 works for you. Test it and turn it on permanently if it works with
124 your system. The magic function %%color_info allows you to toggle this
125 inter- actively for testing."""
126 )
127 ),
128 (('--no-color-info',), dict(
129 action='store_false', dest='InteractiveShell.color_info',
130 help="Disable using colors for info related things.")
131 ),
132 (('--confirm-exit',), dict(
133 action='store_true', dest='InteractiveShell.confirm_exit',
134 help=
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', 'quit' or
137 '%%Exit', you can force a direct exit without any confirmation.
138 """
139 )
140 ),
141 (('--no-confirm-exit',), dict(
142 action='store_false', dest='InteractiveShell.confirm_exit',
143 help="Don't prompt the user when exiting.")
144 ),
145 (('--deep-reload',), dict(
146 action='store_true', dest='InteractiveShell.deep_reload',
147 help=
148 """Enable deep (recursive) reloading by default. IPython can use the
149 deep_reload module which reloads changes in modules recursively (it
150 replaces the reload() function, so you don't need to change anything to
151 use it). deep_reload() forces a full reload of modules whose code may
152 have changed, which the default reload() function does not. When
153 deep_reload is off, IPython will use the normal reload(), but
154 deep_reload will still be available as dreload(). This fea- ture is off
155 by default [which means that you have both normal reload() and
156 dreload()].""")
157 ),
158 (('--no-deep-reload',), dict(
159 action='store_false', dest='InteractiveShell.deep_reload',
160 help="Disable deep (recursive) reloading by default.")
161 ),
162 (('--editor',), dict(
163 type=str, dest='InteractiveShell.editor',
164 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
165 metavar='InteractiveShell.editor')
166 ),
167 (('--log','-l'), dict(
168 action='store_true', dest='InteractiveShell.logstart',
169 help="Start logging to the default log file (./ipython_log.py).")
170 ),
171 (('--logfile','-lf'), dict(
172 type=unicode, dest='InteractiveShell.logfile',
173 help="Start logging to logfile with this name.",
174 metavar='InteractiveShell.logfile')
175 ),
176 (('--log-append','-la'), dict(
177 type=unicode, dest='InteractiveShell.logappend',
178 help="Start logging to the given file in append mode.",
179 metavar='InteractiveShell.logfile')
180 ),
181 (('--pdb',), dict(
182 action='store_true', dest='InteractiveShell.pdb',
183 help="Enable auto calling the pdb debugger after every exception.")
184 ),
185 (('--no-pdb',), dict(
186 action='store_false', dest='InteractiveShell.pdb',
187 help="Disable auto calling the pdb debugger after every exception.")
188 ),
189 (('--pprint',), dict(
190 action='store_true', dest='InteractiveShell.pprint',
191 help="Enable auto pretty printing of results.")
192 ),
193 (('--no-pprint',), dict(
194 action='store_false', dest='InteractiveShell.pprint',
195 help="Disable auto auto pretty printing of results.")
196 ),
197 (('--prompt-in1','-pi1'), dict(
198 type=str, dest='InteractiveShell.prompt_in1',
199 help=
200 """Set the main input prompt ('In [\#]: '). Note that if you are using
201 numbered prompts, the number is represented with a '\#' in the string.
202 Don't forget to quote strings with spaces embedded in them. Most
203 bash-like escapes can be used to customize IPython's prompts, as well
204 as a few additional ones which are IPython-spe- cific. All valid
205 prompt escapes are described in detail in the Customization section of
206 the IPython manual.""",
207 metavar='InteractiveShell.prompt_in1')
208 ),
209 (('--prompt-in2','-pi2'), dict(
210 type=str, dest='InteractiveShell.prompt_in2',
211 help=
212 """Set the secondary input prompt (' .\D.: '). Similar to the previous
213 option, but used for the continuation prompts. The special sequence
214 '\D' is similar to '\#', but with all digits replaced by dots (so you
215 can have your continuation prompt aligned with your input prompt).
216 Default: ' .\D.: ' (note three spaces at the start for alignment with
217 'In [\#]')""",
218 metavar='InteractiveShell.prompt_in2')
219 ),
220 (('--prompt-out','-po'), dict(
221 type=str, dest='InteractiveShell.prompt_out',
222 help="Set the output prompt ('Out[\#]:')",
223 metavar='InteractiveShell.prompt_out')
224 ),
225 (('--quick',), dict(
226 action='store_true', dest='Global.quick',
227 help="Enable quick startup with no config files.")
228 ),
229 (('--readline',), dict(
230 action='store_true', dest='InteractiveShell.readline_use',
231 help="Enable readline for command line usage.")
232 ),
233 (('--no-readline',), dict(
234 action='store_false', dest='InteractiveShell.readline_use',
235 help="Disable readline for command line usage.")
236 ),
237 (('--screen-length','-sl'), dict(
238 type=int, dest='InteractiveShell.screen_length',
239 help=
240 """Number of lines of your screen, used to control printing of very
241 long strings. Strings longer than this number of lines will be sent
242 through a pager instead of directly printed. The default value for
243 this is 0, which means IPython will auto-detect your screen size every
244 time it needs to print certain potentially long strings (this doesn't
245 change the behavior of the 'print' keyword, it's only triggered
246 internally). If for some reason this isn't working well (it needs
247 curses support), specify it yourself. Otherwise don't change the
248 default.""",
249 metavar='InteractiveShell.screen_length')
250 ),
251 (('--separate-in','-si'), dict(
252 type=str, dest='InteractiveShell.separate_in',
253 help="Separator before input prompts. Default '\\n'.",
254 metavar='InteractiveShell.separate_in')
255 ),
256 (('--separate-out','-so'), dict(
257 type=str, dest='InteractiveShell.separate_out',
258 help="Separator before output prompts. Default 0 (nothing).",
259 metavar='InteractiveShell.separate_out')
260 ),
261 (('--separate-out2','-so2'), dict(
262 type=str, dest='InteractiveShell.separate_out2',
263 help="Separator after output prompts. Default 0 (nonight).",
264 metavar='InteractiveShell.separate_out2')
265 ),
266 (('-no-sep',), dict(
267 action='store_true', dest='Global.nosep',
268 help="Eliminate all spacing between prompts.")
269 ),
270 (('--term-title',), dict(
271 action='store_true', dest='InteractiveShell.term_title',
272 help="Enable auto setting the terminal title.")
273 ),
274 (('--no-term-title',), dict(
275 action='store_false', dest='InteractiveShell.term_title',
276 help="Disable auto setting the terminal title.")
277 ),
278 (('--xmode',), dict(
279 type=str, dest='InteractiveShell.xmode',
280 help=
281 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
282 similar to python's normal traceback printing. Context: prints 5 lines
283 of context source code around each line in the traceback. Verbose:
284 similar to Context, but additionally prints the variables currently
285 visible where the exception happened (shortening their strings if too
286 long). This can potentially be very slow, if you happen to have a huge
287 data structure whose string representation is complex to compute.
288 Your computer may appear to freeze for a while with cpu usage at 100%%.
289 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
290 it more than once).
291 """,
292 metavar='InteractiveShell.xmode')
293 ),
294 (('--ext',), dict(
295 type=str, dest='Global.extra_extension',
296 help="The dotted module name of an IPython extension to load.",
297 metavar='Global.extra_extension')
298 ),
299 (('-c',), dict(
300 type=str, dest='Global.code_to_run',
301 help="Execute the given command string.",
302 metavar='Global.code_to_run')
303 ),
304 (('-i',), dict(
305 action='store_true', dest='Global.force_interact',
306 help=
307 "If running code from the command line, become interactive afterwards."
308 )
309 ),
310
311 # Options to start with GUI control enabled from the beginning
312 (('--gui',), dict(
313 type=str, dest='Global.gui',
314 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
315 metavar='gui-mode')
316 ),
317
318 (('--pylab','-pylab'), dict(
319 type=str, dest='Global.pylab',
320 nargs='?', const='auto', metavar='gui-mode',
321 help="Pre-load matplotlib and numpy for interactive use. "+
322 "If no value is given, the gui backend is matplotlib's, else use "+
323 "one of: ['tk', 'qt', 'wx', 'gtk'].")
324 ),
325
326 # Legacy GUI options. Leave them in for backwards compatibility, but the
327 # 'thread' names are really a misnomer now.
328 (('--wthread','-wthread'), dict(
329 action='store_true', dest='Global.wthread',
330 help="Enable wxPython event loop integration "+
331 "(DEPRECATED, use --gui wx)")
332 ),
333 (('--q4thread','--qthread','-q4thread','-qthread'), dict(
334 action='store_true', dest='Global.q4thread',
335 help="Enable Qt4 event loop integration. Qt3 is no longer supported. "+
336 "(DEPRECATED, use --gui qt)")
337 ),
338 (('--gthread','-gthread'), dict(
339 action='store_true', dest='Global.gthread',
340 help="Enable GTK event loop integration. "+
341 "(DEPRECATED, use --gui gtk)")
342 ),
343 )
48
49 class IPAppConfigLoader(BaseAppConfigLoader):
50
51 def _add_arguments(self):
52 super(IPAppConfigLoader, self)._add_arguments()
53 paa = self.parser.add_argument
54 paa('-p',
55 '--profile', dest='Global.profile', type=unicode,
56 help=
57 """The string name of the ipython profile to be used. Assume that your
58 config file is ipython_config-<name>.py (looks in current dir first,
59 then in IPYTHON_DIR). This is a quick way to keep and load multiple
60 config files for different tasks, especially if include your basic one
61 in your more specialized ones. You can keep a basic
62 IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
63 include this one and load extra things for particular tasks.""",
64 metavar='Global.profile')
65 paa('--config-file',
66 dest='Global.config_file', type=unicode,
67 help=
68 """Set the config file name to override default. Normally IPython
69 loads ipython_config.py (from current directory) or
70 IPYTHON_DIR/ipython_config.py. If the loading of your config file
71 fails, IPython starts with a bare bones configuration (no modules
72 loaded at all).""",
73 metavar='Global.config_file')
74 paa('--autocall',
75 dest='InteractiveShell.autocall', type=int,
76 help=
77 """Make IPython automatically call any callable object even if you
78 didn't type explicit parentheses. For example, 'str 43' becomes
79 'str(43)' automatically. The value can be '0' to disable the feature,
80 '1' for 'smart' autocall, where it is not applied if there are no more
81 arguments on the line, and '2' for 'full' autocall, where all callable
82 objects are automatically called (even if no arguments are present).
83 The default is '1'.""",
84 metavar='InteractiveShell.autocall')
85 paa('--autoindent',
86 action='store_true', dest='InteractiveShell.autoindent',
87 help='Turn on autoindenting.')
88 paa('--no-autoindent',
89 action='store_false', dest='InteractiveShell.autoindent',
90 help='Turn off autoindenting.')
91 paa('--automagic',
92 action='store_true', dest='InteractiveShell.automagic',
93 help=
94 """Turn on the auto calling of magic commands. Type %%magic at the
95 IPython prompt for more information.""")
96 paa('--no-automagic',
97 action='store_false', dest='InteractiveShell.automagic',
98 help='Turn off the auto calling of magic commands.')
99 paa('--autoedit-syntax',
100 action='store_true', dest='InteractiveShell.autoedit_syntax',
101 help='Turn on auto editing of files with syntax errors.')
102 paa('--no-autoedit-syntax',
103 action='store_false', dest='InteractiveShell.autoedit_syntax',
104 help='Turn off auto editing of files with syntax errors.')
105 paa('--banner',
106 action='store_true', dest='Global.display_banner',
107 help='Display a banner upon starting IPython.')
108 paa('--no-banner',
109 action='store_false', dest='Global.display_banner',
110 help="Don't display a banner upon starting IPython.")
111 paa('--cache-size',
112 type=int, dest='InteractiveShell.cache_size',
113 help=
114 """Set the size of the output cache. The default is 1000, you can
115 change it permanently in your config file. Setting it to 0 completely
116 disables the caching system, and the minimum value accepted is 20 (if
117 you provide a value less than 20, it is reset to 0 and a warning is
118 issued). This limit is defined because otherwise you'll spend more
119 time re-flushing a too small cache than working""",
120 metavar='InteractiveShell.cache_size')
121 paa('--classic',
122 action='store_true', dest='Global.classic',
123 help="Gives IPython a similar feel to the classic Python prompt.")
124 paa('--colors',
125 type=str, dest='InteractiveShell.colors',
126 help="Set the color scheme (NoColor, Linux, and LightBG).",
127 metavar='InteractiveShell.colors')
128 paa('--color-info',
129 action='store_true', dest='InteractiveShell.color_info',
130 help=
131 """IPython can display information about objects via a set of func-
132 tions, and optionally can use colors for this, syntax highlighting
133 source code and various other elements. However, because this
134 information is passed through a pager (like 'less') and many pagers get
135 confused with color codes, this option is off by default. You can test
136 it and turn it on permanently in your ipython_config.py file if it
137 works for you. Test it and turn it on permanently if it works with
138 your system. The magic function %%color_info allows you to toggle this
139 inter- actively for testing.""")
140 paa('--no-color-info',
141 action='store_false', dest='InteractiveShell.color_info',
142 help="Disable using colors for info related things.")
143 paa('--confirm-exit',
144 action='store_true', dest='InteractiveShell.confirm_exit',
145 help=
146 """Set to confirm when you try to exit IPython with an EOF (Control-D
147 in Unix, Control-Z/Enter in Windows). By typing 'exit', 'quit' or
148 '%%Exit', you can force a direct exit without any confirmation.""")
149 paa('--no-confirm-exit',
150 action='store_false', dest='InteractiveShell.confirm_exit',
151 help="Don't prompt the user when exiting.")
152 paa('--deep-reload',
153 action='store_true', dest='InteractiveShell.deep_reload',
154 help=
155 """Enable deep (recursive) reloading by default. IPython can use the
156 deep_reload module which reloads changes in modules recursively (it
157 replaces the reload() function, so you don't need to change anything to
158 use it). deep_reload() forces a full reload of modules whose code may
159 have changed, which the default reload() function does not. When
160 deep_reload is off, IPython will use the normal reload(), but
161 deep_reload will still be available as dreload(). This fea- ture is off
162 by default [which means that you have both normal reload() and
163 dreload()].""")
164 paa('--no-deep-reload',
165 action='store_false', dest='InteractiveShell.deep_reload',
166 help="Disable deep (recursive) reloading by default.")
167 paa('--editor',
168 type=str, dest='InteractiveShell.editor',
169 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
170 metavar='InteractiveShell.editor')
171 paa('--log','-l',
172 action='store_true', dest='InteractiveShell.logstart',
173 help="Start logging to the default log file (./ipython_log.py).")
174 paa('--logfile','-lf',
175 type=unicode, dest='InteractiveShell.logfile',
176 help="Start logging to logfile with this name.",
177 metavar='InteractiveShell.logfile')
178 paa('--log-append','-la',
179 type=unicode, dest='InteractiveShell.logappend',
180 help="Start logging to the given file in append mode.",
181 metavar='InteractiveShell.logfile')
182 paa('--pdb',
183 action='store_true', dest='InteractiveShell.pdb',
184 help="Enable auto calling the pdb debugger after every exception.")
185 paa('--no-pdb',
186 action='store_false', dest='InteractiveShell.pdb',
187 help="Disable auto calling the pdb debugger after every exception.")
188 paa('--pprint',
189 action='store_true', dest='InteractiveShell.pprint',
190 help="Enable auto pretty printing of results.")
191 paa('--no-pprint',
192 action='store_false', dest='InteractiveShell.pprint',
193 help="Disable auto auto pretty printing of results.")
194 paa('--prompt-in1','-pi1',
195 type=str, dest='InteractiveShell.prompt_in1',
196 help=
197 """Set the main input prompt ('In [\#]: '). Note that if you are using
198 numbered prompts, the number is represented with a '\#' in the string.
199 Don't forget to quote strings with spaces embedded in them. Most
200 bash-like escapes can be used to customize IPython's prompts, as well
201 as a few additional ones which are IPython-spe- cific. All valid
202 prompt escapes are described in detail in the Customization section of
203 the IPython manual.""",
204 metavar='InteractiveShell.prompt_in1')
205 paa('--prompt-in2','-pi2',
206 type=str, dest='InteractiveShell.prompt_in2',
207 help=
208 """Set the secondary input prompt (' .\D.: '). Similar to the previous
209 option, but used for the continuation prompts. The special sequence
210 '\D' is similar to '\#', but with all digits replaced by dots (so you
211 can have your continuation prompt aligned with your input prompt).
212 Default: ' .\D.: ' (note three spaces at the start for alignment with
213 'In [\#]')""",
214 metavar='InteractiveShell.prompt_in2')
215 paa('--prompt-out','-po',
216 type=str, dest='InteractiveShell.prompt_out',
217 help="Set the output prompt ('Out[\#]:')",
218 metavar='InteractiveShell.prompt_out')
219 paa('--quick',
220 action='store_true', dest='Global.quick',
221 help="Enable quick startup with no config files.")
222 paa('--readline',
223 action='store_true', dest='InteractiveShell.readline_use',
224 help="Enable readline for command line usage.")
225 paa('--no-readline',
226 action='store_false', dest='InteractiveShell.readline_use',
227 help="Disable readline for command line usage.")
228 paa('--screen-length','-sl',
229 type=int, dest='InteractiveShell.screen_length',
230 help=
231 """Number of lines of your screen, used to control printing of very
232 long strings. Strings longer than this number of lines will be sent
233 through a pager instead of directly printed. The default value for
234 this is 0, which means IPython will auto-detect your screen size every
235 time it needs to print certain potentially long strings (this doesn't
236 change the behavior of the 'print' keyword, it's only triggered
237 internally). If for some reason this isn't working well (it needs
238 curses support), specify it yourself. Otherwise don't change the
239 default.""",
240 metavar='InteractiveShell.screen_length')
241 paa('--separate-in','-si',
242 type=str, dest='InteractiveShell.separate_in',
243 help="Separator before input prompts. Default '\\n'.",
244 metavar='InteractiveShell.separate_in')
245 paa('--separate-out','-so',
246 type=str, dest='InteractiveShell.separate_out',
247 help="Separator before output prompts. Default 0 (nothing).",
248 metavar='InteractiveShell.separate_out')
249 paa('--separate-out2','-so2',
250 type=str, dest='InteractiveShell.separate_out2',
251 help="Separator after output prompts. Default 0 (nonight).",
252 metavar='InteractiveShell.separate_out2')
253 paa('-no-sep',
254 action='store_true', dest='Global.nosep',
255 help="Eliminate all spacing between prompts.")
256 paa('--term-title',
257 action='store_true', dest='InteractiveShell.term_title',
258 help="Enable auto setting the terminal title.")
259 paa('--no-term-title',
260 action='store_false', dest='InteractiveShell.term_title',
261 help="Disable auto setting the terminal title.")
262 paa('--xmode',
263 type=str, dest='InteractiveShell.xmode',
264 help=
265 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
266 similar to python's normal traceback printing. Context: prints 5 lines
267 of context source code around each line in the traceback. Verbose:
268 similar to Context, but additionally prints the variables currently
269 visible where the exception happened (shortening their strings if too
270 long). This can potentially be very slow, if you happen to have a huge
271 data structure whose string representation is complex to compute.
272 Your computer may appear to freeze for a while with cpu usage at 100%%.
273 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
274 it more than once).
275 """,
276 metavar='InteractiveShell.xmode')
277 paa('--ext',
278 type=str, dest='Global.extra_extension',
279 help="The dotted module name of an IPython extension to load.",
280 metavar='Global.extra_extension')
281 paa('-c',
282 type=str, dest='Global.code_to_run',
283 help="Execute the given command string.",
284 metavar='Global.code_to_run')
285 paa('-i',
286 action='store_true', dest='Global.force_interact',
287 help=
288 "If running code from the command line, become interactive afterwards.")
289
290 # Options to start with GUI control enabled from the beginning
291 paa('--gui',
292 type=str, dest='Global.gui',
293 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
294 metavar='gui-mode')
295 paa('--pylab','-pylab',
296 type=str, dest='Global.pylab',
297 nargs='?', const='auto', metavar='gui-mode',
298 help="Pre-load matplotlib and numpy for interactive use. "+
299 "If no value is given, the gui backend is matplotlib's, else use "+
300 "one of: ['tk', 'qt', 'wx', 'gtk'].")
301
302 # Legacy GUI options. Leave them in for backwards compatibility, but the
303 # 'thread' names are really a misnomer now.
304 paa('--wthread', '-wthread',
305 action='store_true', dest='Global.wthread',
306 help=
307 """Enable wxPython event loop integration. (DEPRECATED, use --gui wx)""")
308 paa('--q4thread', '--qthread', '-q4thread', '-qthread',
309 action='store_true', dest='Global.q4thread',
310 help=
311 """Enable Qt4 event loop integration. Qt3 is no longer supported.
312 (DEPRECATED, use --gui qt)""")
313 paa('--gthread', '-gthread',
314 action='store_true', dest='Global.gthread',
315 help=
316 """Enable GTK event loop integration. (DEPRECATED, use --gui gtk)""")
317
344 318
345 319 #-----------------------------------------------------------------------------
346 320 # Main classes and functions
347 321 #-----------------------------------------------------------------------------
348 322
349 323 class IPythonApp(Application):
350 324 name = u'ipython'
351 325 #: argparse formats better the 'usage' than the 'description' field
352 326 description = None
353 #: usage message printed by argparse. If None, auto-generate
354 327 usage = usage.cl_usage
355
328 command_line_loader = IPAppConfigLoader
356 329 config_file_name = default_config_file_name
357 330
358 cl_arguments = Application.cl_arguments + cl_args
359
360 331 # Private and configuration attributes
361 332 _CrashHandler = crashhandler.IPythonCrashHandler
362
363 def __init__(self, argv=None,
364 constructor_config=None, override_config=None,
365 **shell_params):
366 """Create a new IPythonApp.
367
368 See the parent class for details on how configuration is handled.
369
370 Parameters
371 ----------
372 argv : optional, list
373 If given, used as the command-line argv environment to read arguments
374 from.
375
376 constructor_config : optional, Config
377 If given, additional config that is merged last, after internal
378 defaults, command-line and file-based configs.
379
380 override_config : optional, Config
381 If given, config that overrides all others unconditionally (except
382 for internal defaults, which ensure that all parameters exist).
383
384 shell_params : optional, dict
385 All other keywords are passed to the :class:`iplib.InteractiveShell`
386 constructor.
387 """
388 super(IPythonApp, self).__init__(argv, constructor_config,
389 override_config)
390 self.shell_params = shell_params
391 333
392 334 def create_default_config(self):
393 335 super(IPythonApp, self).create_default_config()
394 336 # Eliminate multiple lookups
395 337 Global = self.default_config.Global
396 338
397 339 # Set all default values
398 340 Global.display_banner = True
399 341
400 342 # If the -c flag is given or a file is given to run at the cmd line
401 343 # like "ipython foo.py", normally we exit without starting the main
402 344 # loop. The force_interact config variable allows a user to override
403 345 # this and interact. It is also set by the -i cmd line flag, just
404 346 # like Python.
405 347 Global.force_interact = False
406 348
407 349 # By default always interact by starting the IPython mainloop.
408 350 Global.interact = True
409 351
410 352 # No GUI integration by default
411 353 Global.gui = False
412 354 # Pylab off by default
413 355 Global.pylab = False
414 356
415 357 # Deprecated versions of gui support that used threading, we support
416 358 # them just for bacwards compatibility as an alternate spelling for
417 359 # '--gui X'
418 360 Global.qthread = False
419 361 Global.q4thread = False
420 362 Global.wthread = False
421 363 Global.gthread = False
422 364
423 365 def load_file_config(self):
424 366 if hasattr(self.command_line_config.Global, 'quick'):
425 367 if self.command_line_config.Global.quick:
426 368 self.file_config = Config()
427 369 return
428 370 super(IPythonApp, self).load_file_config()
429 371
430 372 def post_load_file_config(self):
431 373 if hasattr(self.command_line_config.Global, 'extra_extension'):
432 374 if not hasattr(self.file_config.Global, 'extensions'):
433 375 self.file_config.Global.extensions = []
434 376 self.file_config.Global.extensions.append(
435 377 self.command_line_config.Global.extra_extension)
436 378 del self.command_line_config.Global.extra_extension
437 379
438 380 def pre_construct(self):
439 381 config = self.master_config
440 382
441 383 if hasattr(config.Global, 'classic'):
442 384 if config.Global.classic:
443 385 config.InteractiveShell.cache_size = 0
444 386 config.InteractiveShell.pprint = 0
445 387 config.InteractiveShell.prompt_in1 = '>>> '
446 388 config.InteractiveShell.prompt_in2 = '... '
447 389 config.InteractiveShell.prompt_out = ''
448 390 config.InteractiveShell.separate_in = \
449 391 config.InteractiveShell.separate_out = \
450 392 config.InteractiveShell.separate_out2 = ''
451 393 config.InteractiveShell.colors = 'NoColor'
452 394 config.InteractiveShell.xmode = 'Plain'
453 395
454 396 if hasattr(config.Global, 'nosep'):
455 397 if config.Global.nosep:
456 398 config.InteractiveShell.separate_in = \
457 399 config.InteractiveShell.separate_out = \
458 400 config.InteractiveShell.separate_out2 = ''
459 401
460 402 # if there is code of files to run from the cmd line, don't interact
461 403 # unless the -i flag (Global.force_interact) is true.
462 404 code_to_run = config.Global.get('code_to_run','')
463 405 file_to_run = False
464 406 if self.extra_args and self.extra_args[0]:
465 407 file_to_run = True
466 408 if file_to_run or code_to_run:
467 409 if not config.Global.force_interact:
468 410 config.Global.interact = False
469 411
470 412 def construct(self):
471 413 # I am a little hesitant to put these into InteractiveShell itself.
472 414 # But that might be the place for them
473 415 sys.path.insert(0, '')
474 416
475 417 # Create an InteractiveShell instance
476 self.shell = InteractiveShell(None, self.master_config,
477 **self.shell_params )
418 self.shell = InteractiveShell(None, self.master_config)
478 419
479 420 def post_construct(self):
480 421 """Do actions after construct, but before starting the app."""
481 422 config = self.master_config
482 423
483 424 # shell.display_banner should always be False for the terminal
484 425 # based app, because we call shell.show_banner() by hand below
485 426 # so the banner shows *before* all extension loading stuff.
486 427 self.shell.display_banner = False
487 428
488 429 if config.Global.display_banner and \
489 430 config.Global.interact:
490 431 self.shell.show_banner()
491 432
492 433 # Make sure there is a space below the banner.
493 434 if self.log_level <= logging.INFO: print
494 435
495 436 # Now a variety of things that happen after the banner is printed.
496 437 self._enable_gui_pylab()
497 438 self._load_extensions()
498 439 self._run_exec_lines()
499 440 self._run_exec_files()
500 441 self._run_cmd_line_code()
501 442
502 443 def _enable_gui_pylab(self):
503 444 """Enable GUI event loop integration, taking pylab into account."""
504 445 Global = self.master_config.Global
505 446
506 447 # Select which gui to use
507 448 if Global.gui:
508 449 gui = Global.gui
509 450 # The following are deprecated, but there's likely to be a lot of use
510 451 # of this form out there, so we might as well support it for now. But
511 452 # the --gui option above takes precedence.
512 453 elif Global.wthread:
513 454 gui = inputhook.GUI_WX
514 455 elif Global.qthread:
515 456 gui = inputhook.GUI_QT
516 457 elif Global.gthread:
517 458 gui = inputhook.GUI_GTK
518 459 else:
519 460 gui = None
520 461
521 462 # Using --pylab will also require gui activation, though which toolkit
522 463 # to use may be chosen automatically based on mpl configuration.
523 464 if Global.pylab:
524 465 activate = self.shell.enable_pylab
525 466 if Global.pylab == 'auto':
526 467 gui = None
527 468 else:
528 469 gui = Global.pylab
529 470 else:
530 471 # Enable only GUI integration, no pylab
531 472 activate = inputhook.enable_gui
532 473
533 474 if gui or Global.pylab:
534 475 try:
535 476 self.log.info("Enabling GUI event loop integration, "
536 477 "toolkit=%s, pylab=%s" % (gui, Global.pylab) )
537 478 activate(gui)
538 479 except:
539 480 self.log.warn("Error in enabling GUI event loop integration:")
540 481 self.shell.showtraceback()
541 482
542 483 def _load_extensions(self):
543 484 """Load all IPython extensions in Global.extensions.
544 485
545 486 This uses the :meth:`InteractiveShell.load_extensions` to load all
546 487 the extensions listed in ``self.master_config.Global.extensions``.
547 488 """
548 489 try:
549 490 if hasattr(self.master_config.Global, 'extensions'):
550 491 self.log.debug("Loading IPython extensions...")
551 492 extensions = self.master_config.Global.extensions
552 493 for ext in extensions:
553 494 try:
554 495 self.log.info("Loading IPython extension: %s" % ext)
555 496 self.shell.load_extension(ext)
556 497 except:
557 498 self.log.warn("Error in loading extension: %s" % ext)
558 499 self.shell.showtraceback()
559 500 except:
560 501 self.log.warn("Unknown error in loading extensions:")
561 502 self.shell.showtraceback()
562 503
563 504 def _run_exec_lines(self):
564 505 """Run lines of code in Global.exec_lines in the user's namespace."""
565 506 try:
566 507 if hasattr(self.master_config.Global, 'exec_lines'):
567 508 self.log.debug("Running code from Global.exec_lines...")
568 509 exec_lines = self.master_config.Global.exec_lines
569 510 for line in exec_lines:
570 511 try:
571 512 self.log.info("Running code in user namespace: %s" % line)
572 513 self.shell.runlines(line)
573 514 except:
574 515 self.log.warn("Error in executing line in user namespace: %s" % line)
575 516 self.shell.showtraceback()
576 517 except:
577 518 self.log.warn("Unknown error in handling Global.exec_lines:")
578 519 self.shell.showtraceback()
579 520
580 521 def _exec_file(self, fname):
581 522 full_filename = filefind(fname, [u'.', self.ipython_dir])
582 523 if os.path.isfile(full_filename):
583 524 if full_filename.endswith(u'.py'):
584 525 self.log.info("Running file in user namespace: %s" % full_filename)
585 526 self.shell.safe_execfile(full_filename, self.shell.user_ns)
586 527 elif full_filename.endswith('.ipy'):
587 528 self.log.info("Running file in user namespace: %s" % full_filename)
588 529 self.shell.safe_execfile_ipy(full_filename)
589 530 else:
590 531 self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename)
591 532
592 533 def _run_exec_files(self):
593 534 try:
594 535 if hasattr(self.master_config.Global, 'exec_files'):
595 536 self.log.debug("Running files in Global.exec_files...")
596 537 exec_files = self.master_config.Global.exec_files
597 538 for fname in exec_files:
598 539 self._exec_file(fname)
599 540 except:
600 541 self.log.warn("Unknown error in handling Global.exec_files:")
601 542 self.shell.showtraceback()
602 543
603 544 def _run_cmd_line_code(self):
604 545 if hasattr(self.master_config.Global, 'code_to_run'):
605 546 line = self.master_config.Global.code_to_run
606 547 try:
607 548 self.log.info("Running code given at command line (-c): %s" % line)
608 549 self.shell.runlines(line)
609 550 except:
610 551 self.log.warn("Error in executing line in user namespace: %s" % line)
611 552 self.shell.showtraceback()
612 553 return
613 554 # Like Python itself, ignore the second if the first of these is present
614 555 try:
615 556 fname = self.extra_args[0]
616 557 except:
617 558 pass
618 559 else:
619 560 try:
620 561 self._exec_file(fname)
621 562 except:
622 563 self.log.warn("Error in executing file in user namespace: %s" % fname)
623 564 self.shell.showtraceback()
624 565
625 566 def start_app(self):
626 567 if self.master_config.Global.interact:
627 568 self.log.debug("Starting IPython's mainloop...")
628 569 self.shell.mainloop()
629 570 else:
630 571 self.log.debug("IPython not interactive, start_app is no-op...")
631 572
632 573
633 574 def load_default_config(ipython_dir=None):
634 575 """Load the default config file from the default ipython_dir.
635 576
636 577 This is useful for embedded shells.
637 578 """
638 579 if ipython_dir is None:
639 580 ipython_dir = get_ipython_dir()
640 581 cl = PyFileConfigLoader(default_config_file_name, ipython_dir)
641 582 config = cl.load_config()
642 583 return config
643 584
644 585
645 586 def launch_new_instance():
646 587 """Create and run a full blown IPython instance"""
647 588 app = IPythonApp()
648 589 app.start()
590
@@ -1,464 +1,493 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython cluster directory
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import with_statement
19 19
20 20 import os
21 21 import shutil
22 22 import sys
23 23 import warnings
24 24
25 25 from twisted.python import log
26 26
27 27 from IPython.config.loader import PyFileConfigLoader
28 from IPython.core.application import Application
28 from IPython.core.application import Application, BaseAppConfigLoader
29 29 from IPython.core.component import Component
30 30 from IPython.utils.path import (
31 31 get_ipython_package_dir,
32 32 expand_path
33 33 )
34 34 from IPython.utils.traitlets import Unicode
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Warnings control
38 38 #-----------------------------------------------------------------------------
39 39 # Twisted generates annoying warnings with Python 2.6, as will do other code
40 40 # that imports 'sets' as of today
41 41 warnings.filterwarnings('ignore', 'the sets module is deprecated',
42 42 DeprecationWarning )
43 43
44 44 # This one also comes from Twisted
45 45 warnings.filterwarnings('ignore', 'the sha module is deprecated',
46 46 DeprecationWarning)
47 47
48 48 #-----------------------------------------------------------------------------
49 # Classes and functions
49 # Module errors
50 50 #-----------------------------------------------------------------------------
51 51
52 52 class ClusterDirError(Exception):
53 53 pass
54 54
55 55
56 56 class PIDFileError(Exception):
57 57 pass
58 58
59 59
60 #-----------------------------------------------------------------------------
61 # Class for managing cluster directories
62 #-----------------------------------------------------------------------------
63
60 64 class ClusterDir(Component):
61 65 """An object to manage the cluster directory and its resources.
62 66
63 67 The cluster directory is used by :command:`ipcontroller`,
64 68 :command:`ipcontroller` and :command:`ipcontroller` to manage the
65 69 configuration, logging and security of these applications.
66 70
67 71 This object knows how to find, create and manage these directories. This
68 72 should be used by any code that want's to handle cluster directories.
69 73 """
70 74
71 75 security_dir_name = Unicode('security')
72 76 log_dir_name = Unicode('log')
73 77 pid_dir_name = Unicode('pid')
74 78 security_dir = Unicode(u'')
75 79 log_dir = Unicode(u'')
76 80 pid_dir = Unicode(u'')
77 81 location = Unicode(u'')
78 82
79 83 def __init__(self, location):
80 84 super(ClusterDir, self).__init__(None)
81 85 self.location = location
82 86
83 87 def _location_changed(self, name, old, new):
84 88 if not os.path.isdir(new):
85 89 os.makedirs(new)
86 90 self.security_dir = os.path.join(new, self.security_dir_name)
87 91 self.log_dir = os.path.join(new, self.log_dir_name)
88 92 self.pid_dir = os.path.join(new, self.pid_dir_name)
89 93 self.check_dirs()
90 94
91 95 def _log_dir_changed(self, name, old, new):
92 96 self.check_log_dir()
93 97
94 98 def check_log_dir(self):
95 99 if not os.path.isdir(self.log_dir):
96 100 os.mkdir(self.log_dir)
97 101
98 102 def _security_dir_changed(self, name, old, new):
99 103 self.check_security_dir()
100 104
101 105 def check_security_dir(self):
102 106 if not os.path.isdir(self.security_dir):
103 107 os.mkdir(self.security_dir, 0700)
104 108 os.chmod(self.security_dir, 0700)
105 109
106 110 def _pid_dir_changed(self, name, old, new):
107 111 self.check_pid_dir()
108 112
109 113 def check_pid_dir(self):
110 114 if not os.path.isdir(self.pid_dir):
111 115 os.mkdir(self.pid_dir, 0700)
112 116 os.chmod(self.pid_dir, 0700)
113 117
114 118 def check_dirs(self):
115 119 self.check_security_dir()
116 120 self.check_log_dir()
117 121 self.check_pid_dir()
118 122
119 123 def load_config_file(self, filename):
120 124 """Load a config file from the top level of the cluster dir.
121 125
122 126 Parameters
123 127 ----------
124 128 filename : unicode or str
125 129 The filename only of the config file that must be located in
126 130 the top-level of the cluster directory.
127 131 """
128 132 loader = PyFileConfigLoader(filename, self.location)
129 133 return loader.load_config()
130 134
131 135 def copy_config_file(self, config_file, path=None, overwrite=False):
132 136 """Copy a default config file into the active cluster directory.
133 137
134 138 Default configuration files are kept in :mod:`IPython.config.default`.
135 139 This function moves these from that location to the working cluster
136 140 directory.
137 141 """
138 142 if path is None:
139 143 import IPython.config.default
140 144 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
141 145 path = os.path.sep.join(path)
142 146 src = os.path.join(path, config_file)
143 147 dst = os.path.join(self.location, config_file)
144 148 if not os.path.isfile(dst) or overwrite:
145 149 shutil.copy(src, dst)
146 150
147 151 def copy_all_config_files(self, path=None, overwrite=False):
148 152 """Copy all config files into the active cluster directory."""
149 153 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
150 154 u'ipcluster_config.py']:
151 155 self.copy_config_file(f, path=path, overwrite=overwrite)
152 156
153 157 @classmethod
154 158 def create_cluster_dir(csl, cluster_dir):
155 159 """Create a new cluster directory given a full path.
156 160
157 161 Parameters
158 162 ----------
159 163 cluster_dir : str
160 164 The full path to the cluster directory. If it does exist, it will
161 165 be used. If not, it will be created.
162 166 """
163 167 return ClusterDir(cluster_dir)
164 168
165 169 @classmethod
166 170 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
167 171 """Create a cluster dir by profile name and path.
168 172
169 173 Parameters
170 174 ----------
171 175 path : str
172 176 The path (directory) to put the cluster directory in.
173 177 profile : str
174 178 The name of the profile. The name of the cluster directory will
175 179 be "cluster_<profile>".
176 180 """
177 181 if not os.path.isdir(path):
178 182 raise ClusterDirError('Directory not found: %s' % path)
179 183 cluster_dir = os.path.join(path, u'cluster_' + profile)
180 184 return ClusterDir(cluster_dir)
181 185
182 186 @classmethod
183 187 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
184 188 """Find an existing cluster dir by profile name, return its ClusterDir.
185 189
186 190 This searches through a sequence of paths for a cluster dir. If it
187 191 is not found, a :class:`ClusterDirError` exception will be raised.
188 192
189 193 The search path algorithm is:
190 194 1. ``os.getcwd()``
191 195 2. ``ipython_dir``
192 196 3. The directories found in the ":" separated
193 197 :env:`IPCLUSTER_DIR_PATH` environment variable.
194 198
195 199 Parameters
196 200 ----------
197 201 ipython_dir : unicode or str
198 202 The IPython directory to use.
199 203 profile : unicode or str
200 204 The name of the profile. The name of the cluster directory
201 205 will be "cluster_<profile>".
202 206 """
203 207 dirname = u'cluster_' + profile
204 208 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
205 209 if cluster_dir_paths:
206 210 cluster_dir_paths = cluster_dir_paths.split(':')
207 211 else:
208 212 cluster_dir_paths = []
209 213 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
210 214 for p in paths:
211 215 cluster_dir = os.path.join(p, dirname)
212 216 if os.path.isdir(cluster_dir):
213 217 return ClusterDir(cluster_dir)
214 218 else:
215 219 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
216 220
217 221 @classmethod
218 222 def find_cluster_dir(cls, cluster_dir):
219 223 """Find/create a cluster dir and return its ClusterDir.
220 224
221 225 This will create the cluster directory if it doesn't exist.
222 226
223 227 Parameters
224 228 ----------
225 229 cluster_dir : unicode or str
226 230 The path of the cluster directory. This is expanded using
227 231 :func:`IPython.utils.genutils.expand_path`.
228 232 """
229 233 cluster_dir = expand_path(cluster_dir)
230 234 if not os.path.isdir(cluster_dir):
231 235 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
232 236 return ClusterDir(cluster_dir)
233 237
234 238
235 # Default command line options for IPython cluster applications.
236 cl_args = (
237 (('--ipython-dir',), dict(
238 dest='Global.ipython_dir',type=unicode,
239 help='Set to override default location of Global.ipython_dir.',
240 metavar='Global.ipython_dir') ),
241 (('-p', '--profile',), dict(
242 dest='Global.profile',type=unicode,
243 help=
244 """The string name of the profile to be used. This determines the name
245 of the cluster dir as: cluster_<profile>. The default profile is named
246 'default'. The cluster directory is resolve this way if the
247 --cluster-dir option is not used.""",
248 metavar='Global.profile') ),
249 (('--cluster-dir',), dict(
250 dest='Global.cluster_dir',type=unicode,
251 help="""Set the cluster dir. This overrides the logic used by the
252 --profile option.""",
253 metavar='Global.cluster_dir') ),
254 (('--work-dir',), dict(
255 dest='Global.work_dir',type=unicode,
256 help='Set the working dir for the process.',
257 metavar='Global.work_dir') ),
258 (('--clean-logs',), dict(
259 dest='Global.clean_logs', action='store_true',
260 help='Delete old log flies before starting.') ),
261 (('--no-clean-logs',), dict(
262 dest='Global.clean_logs', action='store_false',
263 help="Don't Delete old log flies before starting.") ),
264 )
239 #-----------------------------------------------------------------------------
240 # Command line options
241 #-----------------------------------------------------------------------------
242
243 class ClusterDirConfigLoader(BaseAppConfigLoader):
244
245 def _add_cluster_profile(self, parser):
246 paa = parser.add_argument
247 paa('-p', '--profile',
248 dest='Global.profile',type=unicode,
249 help=
250 """The string name of the profile to be used. This determines the name
251 of the cluster dir as: cluster_<profile>. The default profile is named
252 'default'. The cluster directory is resolve this way if the
253 --cluster-dir option is not used.""",
254 metavar='Global.profile')
255
256 def _add_cluster_dir(self, parser):
257 paa = parser.add_argument
258 paa('--cluster-dir',
259 dest='Global.cluster_dir',type=unicode,
260 help="""Set the cluster dir. This overrides the logic used by the
261 --profile option.""",
262 metavar='Global.cluster_dir')
263
264 def _add_work_dir(self, parser):
265 paa = parser.add_argument
266 paa('--work-dir',
267 dest='Global.work_dir',type=unicode,
268 help='Set the working dir for the process.',
269 metavar='Global.work_dir')
270
271 def _add_clean_logs(self, parser):
272 paa = parser.add_argument
273 paa('--clean-logs',
274 dest='Global.clean_logs', action='store_true',
275 help='Delete old log flies before starting.')
276
277 def _add_no_clean_logs(self, parser):
278 paa = parser.add_argument
279 paa('--no-clean-logs',
280 dest='Global.clean_logs', action='store_false',
281 help="Don't Delete old log flies before starting.")
282
283 def _add_arguments(self):
284 super(ClusterDirConfigLoader, self)._add_arguments()
285 self._add_cluster_profile(self.parser)
286 self._add_cluster_dir(self.parser)
287 self._add_work_dir(self.parser)
288 self._add_clean_logs(self.parser)
289 self._add_no_clean_logs(self.parser)
265 290
266 291
292 #-----------------------------------------------------------------------------
293 # Main application
294 #-----------------------------------------------------------------------------
295
267 296 class ApplicationWithClusterDir(Application):
268 297 """An application that puts everything into a cluster directory.
269 298
270 299 Instead of looking for things in the ipython_dir, this type of application
271 300 will use its own private directory called the "cluster directory"
272 301 for things like config files, log files, etc.
273 302
274 303 The cluster directory is resolved as follows:
275 304
276 305 * If the ``--cluster-dir`` option is given, it is used.
277 306 * If ``--cluster-dir`` is not given, the application directory is
278 307 resolve using the profile name as ``cluster_<profile>``. The search
279 308 path for this directory is then i) cwd if it is found there
280 309 and ii) in ipython_dir otherwise.
281 310
282 311 The config file for the application is to be put in the cluster
283 312 dir and named the value of the ``config_file_name`` class attribute.
284 313 """
285 314
315 command_line_loader = ClusterDirConfigLoader
286 316 auto_create_cluster_dir = True
287 317
288 cl_arguments = Application.cl_arguments + cl_args
289
290 318 def create_default_config(self):
291 319 super(ApplicationWithClusterDir, self).create_default_config()
292 320 self.default_config.Global.profile = u'default'
293 321 self.default_config.Global.cluster_dir = u''
294 322 self.default_config.Global.work_dir = os.getcwd()
295 323 self.default_config.Global.log_to_file = False
296 324 self.default_config.Global.clean_logs = False
297 325
298 326 def find_resources(self):
299 327 """This resolves the cluster directory.
300 328
301 329 This tries to find the cluster directory and if successful, it will
302 330 have done:
303 331 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
304 332 the application.
305 333 * Sets ``self.cluster_dir`` attribute of the application and config
306 334 objects.
307 335
308 336 The algorithm used for this is as follows:
309 337 1. Try ``Global.cluster_dir``.
310 338 2. Try using ``Global.profile``.
311 339 3. If both of these fail and ``self.auto_create_cluster_dir`` is
312 340 ``True``, then create the new cluster dir in the IPython directory.
313 341 4. If all fails, then raise :class:`ClusterDirError`.
314 342 """
315 343
316 344 try:
317 345 cluster_dir = self.command_line_config.Global.cluster_dir
318 346 except AttributeError:
319 347 cluster_dir = self.default_config.Global.cluster_dir
320 348 cluster_dir = expand_path(cluster_dir)
321 349 try:
322 350 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
323 351 except ClusterDirError:
324 352 pass
325 353 else:
326 354 self.log.info('Using existing cluster dir: %s' % \
327 355 self.cluster_dir_obj.location
328 356 )
329 357 self.finish_cluster_dir()
330 358 return
331 359
332 360 try:
333 361 self.profile = self.command_line_config.Global.profile
334 362 except AttributeError:
335 363 self.profile = self.default_config.Global.profile
336 364 try:
337 365 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
338 366 self.ipython_dir, self.profile)
339 367 except ClusterDirError:
340 368 pass
341 369 else:
342 370 self.log.info('Using existing cluster dir: %s' % \
343 371 self.cluster_dir_obj.location
344 372 )
345 373 self.finish_cluster_dir()
346 374 return
347 375
348 376 if self.auto_create_cluster_dir:
349 377 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
350 378 self.ipython_dir, self.profile
351 379 )
352 380 self.log.info('Creating new cluster dir: %s' % \
353 381 self.cluster_dir_obj.location
354 382 )
355 383 self.finish_cluster_dir()
356 384 else:
357 385 raise ClusterDirError('Could not find a valid cluster directory.')
358 386
359 387 def finish_cluster_dir(self):
360 388 # Set the cluster directory
361 389 self.cluster_dir = self.cluster_dir_obj.location
362 390
363 391 # These have to be set because they could be different from the one
364 392 # that we just computed. Because command line has the highest
365 393 # priority, this will always end up in the master_config.
366 394 self.default_config.Global.cluster_dir = self.cluster_dir
367 395 self.command_line_config.Global.cluster_dir = self.cluster_dir
368 396
369 397 def find_config_file_name(self):
370 398 """Find the config file name for this application."""
371 399 # For this type of Application it should be set as a class attribute.
372 400 if not hasattr(self, 'config_file_name'):
373 401 self.log.critical("No config filename found")
374 402
375 403 def find_config_file_paths(self):
376 404 # Include our own config directory last, so that users can still find
377 405 # our shipped copies of builtin config files even if they don't have
378 406 # them in their ipython cluster directory.
379 407 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
380 408 self.config_file_paths = (self.cluster_dir, conf_dir)
381 409
382 410 def pre_construct(self):
383 411 # The log and security dirs were set earlier, but here we put them
384 412 # into the config and log them.
385 413 config = self.master_config
386 414 sdir = self.cluster_dir_obj.security_dir
387 415 self.security_dir = config.Global.security_dir = sdir
388 416 ldir = self.cluster_dir_obj.log_dir
389 417 self.log_dir = config.Global.log_dir = ldir
390 418 pdir = self.cluster_dir_obj.pid_dir
391 419 self.pid_dir = config.Global.pid_dir = pdir
392 420 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
393 421 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
394 422 # Change to the working directory. We do this just before construct
395 423 # is called so all the components there have the right working dir.
396 424 self.to_work_dir()
397 425
398 426 def to_work_dir(self):
399 427 wd = self.master_config.Global.work_dir
400 428 if unicode(wd) != unicode(os.getcwd()):
401 429 os.chdir(wd)
402 430 self.log.info("Changing to working dir: %s" % wd)
403 431
404 432 def start_logging(self):
405 433 # Remove old log files
406 434 if self.master_config.Global.clean_logs:
407 435 log_dir = self.master_config.Global.log_dir
408 436 for f in os.listdir(log_dir):
409 437 if f.startswith(self.name + u'-') and f.endswith('.log'):
410 438 os.remove(os.path.join(log_dir, f))
411 439 # Start logging to the new log file
412 440 if self.master_config.Global.log_to_file:
413 441 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
414 442 logfile = os.path.join(self.log_dir, log_filename)
415 443 open_log_file = open(logfile, 'w')
416 444 else:
417 445 open_log_file = sys.stdout
418 446 log.startLogging(open_log_file)
419 447
420 448 def write_pid_file(self, overwrite=False):
421 449 """Create a .pid file in the pid_dir with my pid.
422 450
423 451 This must be called after pre_construct, which sets `self.pid_dir`.
424 452 This raises :exc:`PIDFileError` if the pid file exists already.
425 453 """
426 454 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
427 455 if os.path.isfile(pid_file):
428 456 pid = self.get_pid_from_file()
429 457 if not overwrite:
430 458 raise PIDFileError(
431 459 'The pid file [%s] already exists. \nThis could mean that this '
432 460 'server is already running with [pid=%s].' % (pid_file, pid)
433 461 )
434 462 with open(pid_file, 'w') as f:
435 463 self.log.info("Creating pid file: %s" % pid_file)
436 464 f.write(repr(os.getpid())+'\n')
437 465
438 466 def remove_pid_file(self):
439 467 """Remove the pid file.
440 468
441 469 This should be called at shutdown by registering a callback with
442 470 :func:`reactor.addSystemEventTrigger`. This needs to return
443 471 ``None``.
444 472 """
445 473 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
446 474 if os.path.isfile(pid_file):
447 475 try:
448 476 self.log.info("Removing pid file: %s" % pid_file)
449 477 os.remove(pid_file)
450 478 except:
451 479 self.log.warn("Error removing the pid file: %s" % pid_file)
452 480
453 481 def get_pid_from_file(self):
454 482 """Get the pid from the pid file.
455 483
456 484 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
457 485 """
458 486 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
459 487 if os.path.isfile(pid_file):
460 488 with open(pid_file, 'r') as f:
461 489 pid = int(f.read().strip())
462 490 return pid
463 491 else:
464 492 raise PIDFileError('pid file not found: %s' % pid_file)
493
@@ -1,462 +1,459 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The ipcluster application.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import logging
19 19 import os
20 20 import signal
21 21
22 22 if os.name=='posix':
23 23 from twisted.scripts._twistd_unix import daemonize
24 24
25 from IPython.core import release
25 from twisted.internet import reactor, defer
26 from twisted.python import log, failure
27
28
26 29 from IPython.external.argparse import ArgumentParser, SUPPRESS
27 from IPython.config.loader import ArgParseConfigLoader
28 30 from IPython.utils.importstring import import_item
29
30 31 from IPython.kernel.clusterdir import (
31 ApplicationWithClusterDir, ClusterDirError, PIDFileError
32 ApplicationWithClusterDir, ClusterDirConfigLoader,
33 ClusterDirError, PIDFileError
32 34 )
33 35
34 from twisted.internet import reactor, defer
35 from twisted.python import log, failure
36
37 36
38 37 #-----------------------------------------------------------------------------
39 # The ipcluster application
38 # Module level variables
40 39 #-----------------------------------------------------------------------------
41 40
42 41
42 default_config_file_name = u'ipcluster_config.py'
43
44
45 _description = """\
46 Start an IPython cluster for parallel computing.\n\n
47
48 An IPython cluster consists of 1 controller and 1 or more engines.
49 This command automates the startup of these processes using a wide
50 range of startup methods (SSH, local processes, PBS, mpiexec,
51 Windows HPC Server 2008). To start a cluster with 4 engines on your
52 local host simply do "ipcluster start -n 4". For more complex usage
53 you will typically do "ipcluster create -p mycluster", then edit
54 configuration files, followed by "ipcluster start -p mycluster -n 4".
55 """
56
57
43 58 # Exit codes for ipcluster
44 59
45 60 # This will be the exit code if the ipcluster appears to be running because
46 61 # a .pid file exists
47 62 ALREADY_STARTED = 10
48 63
64
49 65 # This will be the exit code if ipcluster stop is run, but there is not .pid
50 66 # file to be found.
51 67 ALREADY_STOPPED = 11
52 68
53 69
54 class IPClusterCLLoader(ArgParseConfigLoader):
70 #-----------------------------------------------------------------------------
71 # Command line options
72 #-----------------------------------------------------------------------------
73
55 74
56 def _add_other_arguments(self):
75 class IPClusterAppConfigLoader(ClusterDirConfigLoader):
76
77 def _add_arguments(self):
78 # Don't call ClusterDirConfigLoader._add_arguments as we don't want
79 # its defaults on self.parser. Instead, we will put those on
80 # default options on our subparsers.
81
57 82 # This has all the common options that all subcommands use
58 parent_parser1 = ArgumentParser(add_help=False,
59 argument_default=SUPPRESS)
60 parent_parser1.add_argument('--ipython-dir',
61 dest='Global.ipython_dir',type=unicode,
62 help='Set to override default location of Global.ipython_dir.',
63 metavar='Global.ipython_dir')
64 parent_parser1.add_argument('--log-level',
65 dest="Global.log_level",type=int,
66 help='Set the log level (0,10,20,30,40,50). Default is 30.',
67 metavar='Global.log_level')
83 parent_parser1 = ArgumentParser(
84 add_help=False,
85 argument_default=SUPPRESS
86 )
87 self._add_ipython_dir(parent_parser1)
88 self._add_log_level(parent_parser1)
68 89
69 90 # This has all the common options that other subcommands use
70 parent_parser2 = ArgumentParser(add_help=False,
71 argument_default=SUPPRESS)
72 parent_parser2.add_argument('-p','--profile',
73 dest='Global.profile',type=unicode,
74 help='The string name of the profile to be used. This determines '
75 'the name of the cluster dir as: cluster_<profile>. The default profile '
76 'is named "default". The cluster directory is resolve this way '
77 'if the --cluster-dir option is not used.',
78 metavar='Global.profile')
79 parent_parser2.add_argument('--cluster-dir',
80 dest='Global.cluster_dir',type=unicode,
81 help='Set the cluster dir. This overrides the logic used by the '
82 '--profile option.',
83 metavar='Global.cluster_dir'),
84 parent_parser2.add_argument('--work-dir',
85 dest='Global.work_dir',type=unicode,
86 help='Set the working dir for the process.',
87 metavar='Global.work_dir')
88 parent_parser2.add_argument('--log-to-file',
89 action='store_true', dest='Global.log_to_file',
90 help='Log to a file in the log directory (default is stdout)'
91 parent_parser2 = ArgumentParser(
92 add_help=False,
93 argument_default=SUPPRESS
91 94 )
95 self._add_cluster_profile(parent_parser2)
96 self._add_cluster_dir(parent_parser2)
97 self._add_work_dir(parent_parser2)
98 paa = parent_parser2.add_argument
99 paa('--log-to-file',
100 action='store_true', dest='Global.log_to_file',
101 help='Log to a file in the log directory (default is stdout)')
92 102
103 # Create the object used to create the subparsers.
93 104 subparsers = self.parser.add_subparsers(
94 105 dest='Global.subcommand',
95 106 title='ipcluster subcommands',
96 description='ipcluster has a variety of subcommands. '
97 'The general way of running ipcluster is "ipcluster <cmd> '
98 ' [options]""',
99 help='For more help, type "ipcluster <cmd> -h"')
107 description=
108 """ipcluster has a variety of subcommands. The general way of
109 running ipcluster is 'ipcluster <cmd> [options]'""",
110 help="For more help, type 'ipcluster <cmd> -h'"
111 )
100 112
113 # The "list" subcommand parser
101 114 parser_list = subparsers.add_parser(
102 115 'list',
103 116 help='List all clusters in cwd and ipython_dir.',
104 117 parents=[parent_parser1]
105 118 )
106 119
120 # The "create" subcommand parser
107 121 parser_create = subparsers.add_parser(
108 122 'create',
109 123 help='Create a new cluster directory.',
110 124 parents=[parent_parser1, parent_parser2]
111 125 )
112 parser_create.add_argument(
113 '--reset-config',
126 paa = parser_create.add_argument
127 paa('--reset-config',
114 128 dest='Global.reset_config', action='store_true',
115 help='Recopy the default config files to the cluster directory. '
116 'You will loose any modifications you have made to these files.'
117 )
129 help=
130 """Recopy the default config files to the cluster directory.
131 You will loose any modifications you have made to these files.""")
118 132
133 # The "start" subcommand parser
119 134 parser_start = subparsers.add_parser(
120 135 'start',
121 136 help='Start a cluster.',
122 137 parents=[parent_parser1, parent_parser2]
123 138 )
124 parser_start.add_argument(
125 '-n', '--number',
139 paa = parser_start.add_argument
140 paa('-n', '--number',
126 141 type=int, dest='Global.n',
127 142 help='The number of engines to start.',
128 metavar='Global.n'
129 )
130 parser_start.add_argument('--clean-logs',
143 metavar='Global.n')
144 paa('--clean-logs',
131 145 dest='Global.clean_logs', action='store_true',
132 help='Delete old log flies before starting.',
133 )
134 parser_start.add_argument('--no-clean-logs',
146 help='Delete old log flies before starting.')
147 paa('--no-clean-logs',
135 148 dest='Global.clean_logs', action='store_false',
136 help="Don't delete old log flies before starting.",
137 )
138 parser_start.add_argument('--daemon',
149 help="Don't delete old log flies before starting.")
150 paa('--daemon',
139 151 dest='Global.daemonize', action='store_true',
140 help='Daemonize the ipcluster program. This implies --log-to-file',
141 )
142 parser_start.add_argument('--no-daemon',
152 help='Daemonize the ipcluster program. This implies --log-to-file')
153 paa('--no-daemon',
143 154 dest='Global.daemonize', action='store_false',
144 help="Dont't daemonize the ipcluster program.",
145 )
155 help="Dont't daemonize the ipcluster program.")
146 156
147 parser_start = subparsers.add_parser(
157 # The "stop" subcommand parser
158 parser_stop = subparsers.add_parser(
148 159 'stop',
149 160 help='Stop a cluster.',
150 161 parents=[parent_parser1, parent_parser2]
151 162 )
152 parser_start.add_argument('--signal',
163 paa = parser_stop.add_argument
164 paa('--signal',
153 165 dest='Global.signal', type=int,
154 166 help="The signal number to use in stopping the cluster (default=2).",
155 metavar="Global.signal",
156 )
157
158
159 default_config_file_name = u'ipcluster_config.py'
167 metavar="Global.signal")
160 168
161 169
162 _description = """Start an IPython cluster for parallel computing.\n\n
163
164 An IPython cluster consists of 1 controller and 1 or more engines.
165 This command automates the startup of these processes using a wide
166 range of startup methods (SSH, local processes, PBS, mpiexec,
167 Windows HPC Server 2008). To start a cluster with 4 engines on your
168 local host simply do "ipcluster start -n 4". For more complex usage
169 you will typically do "ipcluster create -p mycluster", then edit
170 configuration files, followed by "ipcluster start -p mycluster -n 4".
171 """
170 #-----------------------------------------------------------------------------
171 # Main application
172 #-----------------------------------------------------------------------------
172 173
173 174
174 175 class IPClusterApp(ApplicationWithClusterDir):
175 176
176 177 name = u'ipcluster'
177 178 description = _description
179 usage = None
180 command_line_loader = IPClusterAppConfigLoader
178 181 config_file_name = default_config_file_name
179 182 default_log_level = logging.INFO
180 183 auto_create_cluster_dir = False
181 184
182 185 def create_default_config(self):
183 186 super(IPClusterApp, self).create_default_config()
184 187 self.default_config.Global.controller_launcher = \
185 188 'IPython.kernel.launcher.LocalControllerLauncher'
186 189 self.default_config.Global.engine_launcher = \
187 190 'IPython.kernel.launcher.LocalEngineSetLauncher'
188 191 self.default_config.Global.n = 2
189 192 self.default_config.Global.reset_config = False
190 193 self.default_config.Global.clean_logs = True
191 194 self.default_config.Global.signal = 2
192 195 self.default_config.Global.daemonize = False
193 196
194 def create_command_line_config(self):
195 """Create and return a command line config loader."""
196 return IPClusterCLLoader(
197 description=self.description,
198 version=release.version
199 )
200
201 197 def find_resources(self):
202 198 subcommand = self.command_line_config.Global.subcommand
203 199 if subcommand=='list':
204 200 self.list_cluster_dirs()
205 201 # Exit immediately because there is nothing left to do.
206 202 self.exit()
207 203 elif subcommand=='create':
208 204 self.auto_create_cluster_dir = True
209 205 super(IPClusterApp, self).find_resources()
210 206 elif subcommand=='start' or subcommand=='stop':
211 207 self.auto_create_cluster_dir = True
212 208 try:
213 209 super(IPClusterApp, self).find_resources()
214 210 except ClusterDirError:
215 211 raise ClusterDirError(
216 212 "Could not find a cluster directory. A cluster dir must "
217 213 "be created before running 'ipcluster start'. Do "
218 214 "'ipcluster create -h' or 'ipcluster list -h' for more "
219 215 "information about creating and listing cluster dirs."
220 216 )
221 217
222 218 def list_cluster_dirs(self):
223 219 # Find the search paths
224 220 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
225 221 if cluster_dir_paths:
226 222 cluster_dir_paths = cluster_dir_paths.split(':')
227 223 else:
228 224 cluster_dir_paths = []
229 225 try:
230 226 ipython_dir = self.command_line_config.Global.ipython_dir
231 227 except AttributeError:
232 228 ipython_dir = self.default_config.Global.ipython_dir
233 229 paths = [os.getcwd(), ipython_dir] + \
234 230 cluster_dir_paths
235 231 paths = list(set(paths))
236 232
237 233 self.log.info('Searching for cluster dirs in paths: %r' % paths)
238 234 for path in paths:
239 235 files = os.listdir(path)
240 236 for f in files:
241 237 full_path = os.path.join(path, f)
242 238 if os.path.isdir(full_path) and f.startswith('cluster_'):
243 239 profile = full_path.split('_')[-1]
244 240 start_cmd = 'ipcluster start -p %s -n 4' % profile
245 241 print start_cmd + " ==> " + full_path
246 242
247 243 def pre_construct(self):
248 244 # IPClusterApp.pre_construct() is where we cd to the working directory.
249 245 super(IPClusterApp, self).pre_construct()
250 246 config = self.master_config
251 247 try:
252 248 daemon = config.Global.daemonize
253 249 if daemon:
254 250 config.Global.log_to_file = True
255 251 except AttributeError:
256 252 pass
257 253
258 254 def construct(self):
259 255 config = self.master_config
260 256 subcmd = config.Global.subcommand
261 257 reset = config.Global.reset_config
262 258 if subcmd == 'list':
263 259 return
264 260 if subcmd == 'create':
265 261 self.log.info('Copying default config files to cluster directory '
266 262 '[overwrite=%r]' % (reset,))
267 263 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
268 264 if subcmd =='start':
269 265 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
270 266 self.start_logging()
271 267 reactor.callWhenRunning(self.start_launchers)
272 268
273 269 def start_launchers(self):
274 270 config = self.master_config
275 271
276 272 # Create the launchers. In both bases, we set the work_dir of
277 273 # the launcher to the cluster_dir. This is where the launcher's
278 274 # subprocesses will be launched. It is not where the controller
279 275 # and engine will be launched.
280 276 el_class = import_item(config.Global.engine_launcher)
281 277 self.engine_launcher = el_class(
282 278 work_dir=self.cluster_dir, config=config
283 279 )
284 280 cl_class = import_item(config.Global.controller_launcher)
285 281 self.controller_launcher = cl_class(
286 282 work_dir=self.cluster_dir, config=config
287 283 )
288 284
289 285 # Setup signals
290 286 signal.signal(signal.SIGINT, self.sigint_handler)
291 287
292 288 # Setup the observing of stopping. If the controller dies, shut
293 289 # everything down as that will be completely fatal for the engines.
294 290 d1 = self.controller_launcher.observe_stop()
295 291 d1.addCallback(self.stop_launchers)
296 292 # But, we don't monitor the stopping of engines. An engine dying
297 293 # is just fine and in principle a user could start a new engine.
298 294 # Also, if we did monitor engine stopping, it is difficult to
299 295 # know what to do when only some engines die. Currently, the
300 296 # observing of engine stopping is inconsistent. Some launchers
301 297 # might trigger on a single engine stopping, other wait until
302 298 # all stop. TODO: think more about how to handle this.
303 299
304 300 # Start the controller and engines
305 301 self._stopping = False # Make sure stop_launchers is not called 2x.
306 302 d = self.start_controller()
307 303 d.addCallback(self.start_engines)
308 304 d.addCallback(self.startup_message)
309 305 # If the controller or engines fail to start, stop everything
310 306 d.addErrback(self.stop_launchers)
311 307 return d
312 308
313 309 def startup_message(self, r=None):
314 310 log.msg("IPython cluster: started")
315 311 return r
316 312
317 313 def start_controller(self, r=None):
318 314 # log.msg("In start_controller")
319 315 config = self.master_config
320 316 d = self.controller_launcher.start(
321 317 cluster_dir=config.Global.cluster_dir
322 318 )
323 319 return d
324 320
325 321 def start_engines(self, r=None):
326 322 # log.msg("In start_engines")
327 323 config = self.master_config
328 324 d = self.engine_launcher.start(
329 325 config.Global.n,
330 326 cluster_dir=config.Global.cluster_dir
331 327 )
332 328 return d
333 329
334 330 def stop_controller(self, r=None):
335 331 # log.msg("In stop_controller")
336 332 if self.controller_launcher.running:
337 333 d = self.controller_launcher.stop()
338 334 d.addErrback(self.log_err)
339 335 return d
340 336 else:
341 337 return defer.succeed(None)
342 338
343 339 def stop_engines(self, r=None):
344 340 # log.msg("In stop_engines")
345 341 if self.engine_launcher.running:
346 342 d = self.engine_launcher.stop()
347 343 d.addErrback(self.log_err)
348 344 return d
349 345 else:
350 346 return defer.succeed(None)
351 347
352 348 def log_err(self, f):
353 349 log.msg(f.getTraceback())
354 350 return None
355 351
356 352 def stop_launchers(self, r=None):
357 353 if not self._stopping:
358 354 self._stopping = True
359 355 if isinstance(r, failure.Failure):
360 356 log.msg('Unexpected error in ipcluster:')
361 357 log.msg(r.getTraceback())
362 358 log.msg("IPython cluster: stopping")
363 359 # These return deferreds. We are not doing anything with them
364 360 # but we are holding refs to them as a reminder that they
365 361 # do return deferreds.
366 362 d1 = self.stop_engines()
367 363 d2 = self.stop_controller()
368 364 # Wait a few seconds to let things shut down.
369 365 reactor.callLater(4.0, reactor.stop)
370 366
371 367 def sigint_handler(self, signum, frame):
372 368 self.stop_launchers()
373 369
374 370 def start_logging(self):
375 371 # Remove old log files of the controller and engine
376 372 if self.master_config.Global.clean_logs:
377 373 log_dir = self.master_config.Global.log_dir
378 374 for f in os.listdir(log_dir):
379 375 if f.startswith('ipengine' + '-'):
380 376 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
381 377 os.remove(os.path.join(log_dir, f))
382 378 if f.startswith('ipcontroller' + '-'):
383 379 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
384 380 os.remove(os.path.join(log_dir, f))
385 381 # This will remote old log files for ipcluster itself
386 382 super(IPClusterApp, self).start_logging()
387 383
388 384 def start_app(self):
389 385 """Start the application, depending on what subcommand is used."""
390 386 subcmd = self.master_config.Global.subcommand
391 387 if subcmd=='create' or subcmd=='list':
392 388 return
393 389 elif subcmd=='start':
394 390 self.start_app_start()
395 391 elif subcmd=='stop':
396 392 self.start_app_stop()
397 393
398 394 def start_app_start(self):
399 395 """Start the app for the start subcommand."""
400 396 config = self.master_config
401 397 # First see if the cluster is already running
402 398 try:
403 399 pid = self.get_pid_from_file()
404 400 except PIDFileError:
405 401 pass
406 402 else:
407 403 self.log.critical(
408 404 'Cluster is already running with [pid=%s]. '
409 405 'use "ipcluster stop" to stop the cluster.' % pid
410 406 )
411 407 # Here I exit with a unusual exit status that other processes
412 408 # can watch for to learn how I existed.
413 409 self.exit(ALREADY_STARTED)
414 410
415 411 # Now log and daemonize
416 412 self.log.info(
417 413 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
418 414 )
419 415 # TODO: Get daemonize working on Windows or as a Windows Server.
420 416 if config.Global.daemonize:
421 417 if os.name=='posix':
422 418 daemonize()
423 419
424 420 # Now write the new pid file AFTER our new forked pid is active.
425 421 self.write_pid_file()
426 422 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
427 423 reactor.run()
428 424
429 425 def start_app_stop(self):
430 426 """Start the app for the stop subcommand."""
431 427 config = self.master_config
432 428 try:
433 429 pid = self.get_pid_from_file()
434 430 except PIDFileError:
435 431 self.log.critical(
436 432 'Problem reading pid file, cluster is probably not running.'
437 433 )
438 434 # Here I exit with a unusual exit status that other processes
439 435 # can watch for to learn how I existed.
440 436 self.exit(ALREADY_STOPPED)
441 437 else:
442 438 if os.name=='posix':
443 439 sig = config.Global.signal
444 440 self.log.info(
445 441 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
446 442 )
447 443 os.kill(pid, sig)
448 444 elif os.name=='nt':
449 445 # As of right now, we don't support daemonize on Windows, so
450 446 # stop will not do anything. Minimally, it should clean up the
451 447 # old .pid files.
452 448 self.remove_pid_file()
453 449
450
454 451 def launch_new_instance():
455 452 """Create and run the IPython cluster."""
456 453 app = IPClusterApp()
457 454 app.start()
458 455
459 456
460 457 if __name__ == '__main__':
461 458 launch_new_instance()
462 459
@@ -1,253 +1,260 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython controller application.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import with_statement
19 19
20 20 import copy
21 21 import sys
22 22
23 23 from twisted.application import service
24 24 from twisted.internet import reactor
25 25 from twisted.python import log
26 26
27 27 from IPython.config.loader import Config
28 from IPython.core.application import Application
29 28 from IPython.kernel import controllerservice
30 from IPython.kernel.clusterdir import ApplicationWithClusterDir
29 from IPython.kernel.clusterdir import (
30 ApplicationWithClusterDir,
31 ClusterDirConfigLoader
32 )
31 33 from IPython.kernel.fcutil import FCServiceFactory
32 34 from IPython.utils.traitlets import Instance, Unicode
33 35
36
37 #-----------------------------------------------------------------------------
38 # Module level variables
39 #-----------------------------------------------------------------------------
40
41
42 #: The default config file name for this application
43 default_config_file_name = u'ipcontroller_config.py'
44
45
46 _description = """Start the IPython controller for parallel computing.
47
48 The IPython controller provides a gateway between the IPython engines and
49 clients. The controller needs to be started before the engines and can be
50 configured using command line options or using a cluster directory. Cluster
51 directories contain config, log and security files and are usually located in
52 your .ipython directory and named as "cluster_<profile>". See the --profile
53 and --cluster-dir options for details.
54 """
55
34 56 #-----------------------------------------------------------------------------
35 57 # Default interfaces
36 58 #-----------------------------------------------------------------------------
37 59
38 60 # The default client interfaces for FCClientServiceFactory.interfaces
39 61 default_client_interfaces = Config()
40 62 default_client_interfaces.Task.interface_chain = [
41 63 'IPython.kernel.task.ITaskController',
42 64 'IPython.kernel.taskfc.IFCTaskController'
43 65 ]
44 66
45 67 default_client_interfaces.Task.furl_file = 'ipcontroller-tc.furl'
46 68
47 69 default_client_interfaces.MultiEngine.interface_chain = [
48 70 'IPython.kernel.multiengine.IMultiEngine',
49 71 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine'
50 72 ]
51 73
52 74 default_client_interfaces.MultiEngine.furl_file = u'ipcontroller-mec.furl'
53 75
54 76 # Make this a dict we can pass to Config.__init__ for the default
55 77 default_client_interfaces = dict(copy.deepcopy(default_client_interfaces.items()))
56 78
57 79
58 80
59 81 # The default engine interfaces for FCEngineServiceFactory.interfaces
60 82 default_engine_interfaces = Config()
61 83 default_engine_interfaces.Default.interface_chain = [
62 84 'IPython.kernel.enginefc.IFCControllerBase'
63 85 ]
64 86
65 87 default_engine_interfaces.Default.furl_file = u'ipcontroller-engine.furl'
66 88
67 89 # Make this a dict we can pass to Config.__init__ for the default
68 90 default_engine_interfaces = dict(copy.deepcopy(default_engine_interfaces.items()))
69 91
70 92
71 93 #-----------------------------------------------------------------------------
72 94 # Service factories
73 95 #-----------------------------------------------------------------------------
74 96
75 97
76 98 class FCClientServiceFactory(FCServiceFactory):
77 99 """A Foolscap implementation of the client services."""
78 100
79 101 cert_file = Unicode(u'ipcontroller-client.pem', config=True)
80 102 interfaces = Instance(klass=Config, kw=default_client_interfaces,
81 103 allow_none=False, config=True)
82 104
83 105
84 106 class FCEngineServiceFactory(FCServiceFactory):
85 107 """A Foolscap implementation of the engine services."""
86 108
87 109 cert_file = Unicode(u'ipcontroller-engine.pem', config=True)
88 110 interfaces = Instance(klass=dict, kw=default_engine_interfaces,
89 111 allow_none=False, config=True)
90 112
91 113
92 114 #-----------------------------------------------------------------------------
93 # The main application
115 # Command line options
94 116 #-----------------------------------------------------------------------------
95 117
96 118
97 cl_args = (
98 # Client config
99 (('--client-ip',), dict(
100 type=str, dest='FCClientServiceFactory.ip',
101 help='The IP address or hostname the controller will listen on for '
102 'client connections.',
103 metavar='FCClientServiceFactory.ip')
104 ),
105 (('--client-port',), dict(
106 type=int, dest='FCClientServiceFactory.port',
107 help='The port the controller will listen on for client connections. '
108 'The default is to use 0, which will autoselect an open port.',
109 metavar='FCClientServiceFactory.port')
110 ),
111 (('--client-location',), dict(
112 type=str, dest='FCClientServiceFactory.location',
113 help='The hostname or IP that clients should connect to. This does '
114 'not control which interface the controller listens on. Instead, this '
115 'determines the hostname/IP that is listed in the FURL, which is how '
116 'clients know where to connect. Useful if the controller is listening '
117 'on multiple interfaces.',
118 metavar='FCClientServiceFactory.location')
119 ),
120 # Engine config
121 (('--engine-ip',), dict(
122 type=str, dest='FCEngineServiceFactory.ip',
123 help='The IP address or hostname the controller will listen on for '
124 'engine connections.',
125 metavar='FCEngineServiceFactory.ip')
126 ),
127 (('--engine-port',), dict(
128 type=int, dest='FCEngineServiceFactory.port',
129 help='The port the controller will listen on for engine connections. '
130 'The default is to use 0, which will autoselect an open port.',
131 metavar='FCEngineServiceFactory.port')
132 ),
133 (('--engine-location',), dict(
134 type=str, dest='FCEngineServiceFactory.location',
135 help='The hostname or IP that engines should connect to. This does '
136 'not control which interface the controller listens on. Instead, this '
137 'determines the hostname/IP that is listed in the FURL, which is how '
138 'engines know where to connect. Useful if the controller is listening '
139 'on multiple interfaces.',
140 metavar='FCEngineServiceFactory.location')
141 ),
142 # Global config
143 (('--log-to-file',), dict(
144 action='store_true', dest='Global.log_to_file',
145 help='Log to a file in the log directory (default is stdout)')
146 ),
147 (('-r','--reuse-furls'), dict(
148 action='store_true', dest='Global.reuse_furls',
149 help='Try to reuse all FURL files. If this is not set all FURL files '
150 'are deleted before the controller starts. This must be set if '
151 'specific ports are specified by --engine-port or --client-port.')
152 ),
153 (('--no-secure',), dict(
154 action='store_false', dest='Global.secure',
155 help='Turn off SSL encryption for all connections.')
156 ),
157 (('--secure',), dict(
158 action='store_true', dest='Global.secure',
159 help='Turn off SSL encryption for all connections.')
160 )
161 )
119 class IPControllerAppConfigLoader(ClusterDirConfigLoader):
120
121 def _add_arguments(self):
122 super(IPControllerAppConfigLoader, self)._add_arguments()
123 paa = self.parser.add_argument
124 # Client config
125 paa('--client-ip',
126 type=str, dest='FCClientServiceFactory.ip',
127 help='The IP address or hostname the controller will listen on for '
128 'client connections.',
129 metavar='FCClientServiceFactory.ip')
130 paa('--client-port',
131 type=int, dest='FCClientServiceFactory.port',
132 help='The port the controller will listen on for client connections. '
133 'The default is to use 0, which will autoselect an open port.',
134 metavar='FCClientServiceFactory.port')
135 paa('--client-location',), dict(
136 type=str, dest='FCClientServiceFactory.location',
137 help='The hostname or IP that clients should connect to. This does '
138 'not control which interface the controller listens on. Instead, this '
139 'determines the hostname/IP that is listed in the FURL, which is how '
140 'clients know where to connect. Useful if the controller is listening '
141 'on multiple interfaces.',
142 metavar='FCClientServiceFactory.location')
143 # Engine config
144 paa('--engine-ip',
145 type=str, dest='FCEngineServiceFactory.ip',
146 help='The IP address or hostname the controller will listen on for '
147 'engine connections.',
148 metavar='FCEngineServiceFactory.ip')
149 paa('--engine-port',
150 type=int, dest='FCEngineServiceFactory.port',
151 help='The port the controller will listen on for engine connections. '
152 'The default is to use 0, which will autoselect an open port.',
153 metavar='FCEngineServiceFactory.port')
154 paa('--engine-location',
155 type=str, dest='FCEngineServiceFactory.location',
156 help='The hostname or IP that engines should connect to. This does '
157 'not control which interface the controller listens on. Instead, this '
158 'determines the hostname/IP that is listed in the FURL, which is how '
159 'engines know where to connect. Useful if the controller is listening '
160 'on multiple interfaces.',
161 metavar='FCEngineServiceFactory.location')
162 # Global config
163 paa('--log-to-file',
164 action='store_true', dest='Global.log_to_file',
165 help='Log to a file in the log directory (default is stdout)')
166 paa('-r','--reuse-furls',
167 action='store_true', dest='Global.reuse_furls',
168 help='Try to reuse all FURL files. If this is not set all FURL files '
169 'are deleted before the controller starts. This must be set if '
170 'specific ports are specified by --engine-port or --client-port.')
171 paa('--no-secure',
172 action='store_false', dest='Global.secure',
173 help='Turn off SSL encryption for all connections.')
174 paa('--secure',
175 action='store_true', dest='Global.secure',
176 help='Turn off SSL encryption for all connections.')
162 177
163 178
164 _description = """Start the IPython controller for parallel computing.
165
166 The IPython controller provides a gateway between the IPython engines and
167 clients. The controller needs to be started before the engines and can be
168 configured using command line options or using a cluster directory. Cluster
169 directories contain config, log and security files and are usually located in
170 your .ipython directory and named as "cluster_<profile>". See the --profile
171 and --cluster-dir options for details.
172 """
173
174 default_config_file_name = u'ipcontroller_config.py'
179 #-----------------------------------------------------------------------------
180 # The main application
181 #-----------------------------------------------------------------------------
175 182
176 183
177 184 class IPControllerApp(ApplicationWithClusterDir):
178 185
179 186 name = u'ipcontroller'
180 187 description = _description
188 command_line_loader = IPControllerAppConfigLoader
181 189 config_file_name = default_config_file_name
182 190 auto_create_cluster_dir = True
183 cl_arguments = Application.cl_arguments + cl_args
184 191
185 192 def create_default_config(self):
186 193 super(IPControllerApp, self).create_default_config()
187 194 self.default_config.Global.reuse_furls = False
188 195 self.default_config.Global.secure = True
189 196 self.default_config.Global.import_statements = []
190 197 self.default_config.Global.clean_logs = True
191 198
192 199 def post_load_command_line_config(self):
193 200 # Now setup reuse_furls
194 201 c = self.command_line_config
195 202 if hasattr(c.Global, 'reuse_furls'):
196 203 c.FCClientServiceFactory.reuse_furls = c.Global.reuse_furls
197 204 c.FCEngineServiceFactory.reuse_furls = c.Global.reuse_furls
198 205 del c.Global.reuse_furls
199 206 if hasattr(c.Global, 'secure'):
200 207 c.FCClientServiceFactory.secure = c.Global.secure
201 208 c.FCEngineServiceFactory.secure = c.Global.secure
202 209 del c.Global.secure
203 210
204 211 def construct(self):
205 212 # This is the working dir by now.
206 213 sys.path.insert(0, '')
207 214
208 215 self.start_logging()
209 216 self.import_statements()
210 217
211 218 # Create the service hierarchy
212 219 self.main_service = service.MultiService()
213 220 # The controller service
214 221 controller_service = controllerservice.ControllerService()
215 222 controller_service.setServiceParent(self.main_service)
216 223 # The client tub and all its refereceables
217 224 csfactory = FCClientServiceFactory(self.master_config, controller_service)
218 225 client_service = csfactory.create()
219 226 client_service.setServiceParent(self.main_service)
220 227 # The engine tub
221 228 esfactory = FCEngineServiceFactory(self.master_config, controller_service)
222 229 engine_service = esfactory.create()
223 230 engine_service.setServiceParent(self.main_service)
224 231
225 232 def import_statements(self):
226 233 statements = self.master_config.Global.import_statements
227 234 for s in statements:
228 235 try:
229 236 log.msg("Executing statement: '%s'" % s)
230 237 exec s in globals(), locals()
231 238 except:
232 239 log.msg("Error running statement: %s" % s)
233 240
234 241 def start_app(self):
235 242 # Start the controller service.
236 243 self.main_service.startService()
237 244 # Write the .pid file overwriting old ones. This allow multiple
238 245 # controllers to clober each other. But Windows is not cleaning
239 246 # these up properly.
240 247 self.write_pid_file(overwrite=True)
241 248 # Add a trigger to delete the .pid file upon shutting down.
242 249 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
243 250 reactor.run()
244 251
245 252
246 253 def launch_new_instance():
247 254 """Create and run the IPython controller"""
248 255 app = IPControllerApp()
249 256 app.start()
250 257
251 258
252 259 if __name__ == '__main__':
253 260 launch_new_instance()
@@ -1,229 +1,242 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython controller application
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import os
19 19 import sys
20 20
21 21 from twisted.application import service
22 22 from twisted.internet import reactor
23 23 from twisted.python import log
24 24
25 from IPython.core.application import Application
26 from IPython.kernel.clusterdir import ApplicationWithClusterDir
25 from IPython.kernel.clusterdir import (
26 ApplicationWithClusterDir,
27 ClusterDirConfigLoader
28 )
27 29 from IPython.kernel.engineconnector import EngineConnector
28 30 from IPython.kernel.engineservice import EngineService
29 31 from IPython.kernel.fcutil import Tub
30 32 from IPython.utils.importstring import import_item
31 33
32 34 #-----------------------------------------------------------------------------
33 # The main application
35 # Module level variables
34 36 #-----------------------------------------------------------------------------
35 37
36 cl_args = (
37 # Controller config
38 (('--furl-file',), dict(
39 type=unicode, dest='Global.furl_file',
40 help='The full location of the file containing the FURL of the '
41 'controller. If this is not given, the FURL file must be in the '
42 'security directory of the cluster directory. This location is '
43 'resolved using the --profile and --app-dir options.',
44 metavar='Global.furl_file')
45 ),
46 # MPI
47 (('--mpi',), dict(
48 type=str, dest='MPI.use',
49 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
50 metavar='MPI.use')
51 ),
52 # Global config
53 (('--log-to-file',), dict(
54 action='store_true', dest='Global.log_to_file',
55 help='Log to a file in the log directory (default is stdout)')
56 )
57 )
38 #: The default config file name for this application
39 default_config_file_name = u'ipengine_config.py'
58 40
59 41
60 42 mpi4py_init = """from mpi4py import MPI as mpi
61 43 mpi.size = mpi.COMM_WORLD.Get_size()
62 44 mpi.rank = mpi.COMM_WORLD.Get_rank()
63 45 """
64 46
47
65 48 pytrilinos_init = """from PyTrilinos import Epetra
66 49 class SimpleStruct:
67 50 pass
68 51 mpi = SimpleStruct()
69 52 mpi.rank = 0
70 53 mpi.size = 0
71 54 """
72 55
73 56
74 default_config_file_name = u'ipengine_config.py'
75
76
77 57 _description = """Start an IPython engine for parallel computing.\n\n
78 58
79 59 IPython engines run in parallel and perform computations on behalf of a client
80 60 and controller. A controller needs to be started before the engines. The
81 61 engine can be configured using command line options or using a cluster
82 62 directory. Cluster directories contain config, log and security files and are
83 63 usually located in your .ipython directory and named as "cluster_<profile>".
84 64 See the --profile and --cluster-dir options for details.
85 65 """
86 66
67 #-----------------------------------------------------------------------------
68 # Command line options
69 #-----------------------------------------------------------------------------
70
71
72 class IPEngineAppConfigLoader(ClusterDirConfigLoader):
73
74 def _add_arguments(self):
75 super(IPEngineAppConfigLoader, self)._add_arguments()
76 paa = self.parser.add_argument
77 # Controller config
78 paa('--furl-file',
79 type=unicode, dest='Global.furl_file',
80 help='The full location of the file containing the FURL of the '
81 'controller. If this is not given, the FURL file must be in the '
82 'security directory of the cluster directory. This location is '
83 'resolved using the --profile and --app-dir options.',
84 metavar='Global.furl_file')
85 # MPI
86 paa('--mpi',
87 type=str, dest='MPI.use',
88 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).',
89 metavar='MPI.use')
90 # Global config
91 paa('--log-to-file',
92 action='store_true', dest='Global.log_to_file',
93 help='Log to a file in the log directory (default is stdout)')
94
95
96 #-----------------------------------------------------------------------------
97 # Main application
98 #-----------------------------------------------------------------------------
99
87 100
88 101 class IPEngineApp(ApplicationWithClusterDir):
89 102
90 103 name = u'ipengine'
91 104 description = _description
105 command_line_loader = IPEngineAppConfigLoader
92 106 config_file_name = default_config_file_name
93 107 auto_create_cluster_dir = True
94 cl_arguments = Application.cl_arguments + cl_args
95 108
96 109 def create_default_config(self):
97 110 super(IPEngineApp, self).create_default_config()
98 111
99 112 # The engine should not clean logs as we don't want to remove the
100 113 # active log files of other running engines.
101 114 self.default_config.Global.clean_logs = False
102 115
103 116 # Global config attributes
104 117 self.default_config.Global.exec_lines = []
105 118 self.default_config.Global.shell_class = 'IPython.kernel.core.interpreter.Interpreter'
106 119
107 120 # Configuration related to the controller
108 121 # This must match the filename (path not included) that the controller
109 122 # used for the FURL file.
110 123 self.default_config.Global.furl_file_name = u'ipcontroller-engine.furl'
111 124 # If given, this is the actual location of the controller's FURL file.
112 125 # If not, this is computed using the profile, app_dir and furl_file_name
113 126 self.default_config.Global.furl_file = u''
114 127
115 128 # The max number of connection attemps and the initial delay between
116 129 # those attemps.
117 130 self.default_config.Global.connect_delay = 0.1
118 131 self.default_config.Global.connect_max_tries = 15
119 132
120 133 # MPI related config attributes
121 134 self.default_config.MPI.use = ''
122 135 self.default_config.MPI.mpi4py = mpi4py_init
123 136 self.default_config.MPI.pytrilinos = pytrilinos_init
124 137
125 138 def post_load_command_line_config(self):
126 139 pass
127 140
128 141 def pre_construct(self):
129 142 super(IPEngineApp, self).pre_construct()
130 143 self.find_cont_furl_file()
131 144
132 145 def find_cont_furl_file(self):
133 146 """Set the furl file.
134 147
135 148 Here we don't try to actually see if it exists for is valid as that
136 149 is hadled by the connection logic.
137 150 """
138 151 config = self.master_config
139 152 # Find the actual controller FURL file
140 153 if not config.Global.furl_file:
141 154 try_this = os.path.join(
142 155 config.Global.cluster_dir,
143 156 config.Global.security_dir,
144 157 config.Global.furl_file_name
145 158 )
146 159 config.Global.furl_file = try_this
147 160
148 161 def construct(self):
149 162 # This is the working dir by now.
150 163 sys.path.insert(0, '')
151 164
152 165 self.start_mpi()
153 166 self.start_logging()
154 167
155 168 # Create the underlying shell class and EngineService
156 169 shell_class = import_item(self.master_config.Global.shell_class)
157 170 self.engine_service = EngineService(shell_class, mpi=mpi)
158 171
159 172 self.exec_lines()
160 173
161 174 # Create the service hierarchy
162 175 self.main_service = service.MultiService()
163 176 self.engine_service.setServiceParent(self.main_service)
164 177 self.tub_service = Tub()
165 178 self.tub_service.setServiceParent(self.main_service)
166 179 # This needs to be called before the connection is initiated
167 180 self.main_service.startService()
168 181
169 182 # This initiates the connection to the controller and calls
170 183 # register_engine to tell the controller we are ready to do work
171 184 self.engine_connector = EngineConnector(self.tub_service)
172 185
173 186 log.msg("Using furl file: %s" % self.master_config.Global.furl_file)
174 187
175 188 reactor.callWhenRunning(self.call_connect)
176 189
177 190 def call_connect(self):
178 191 d = self.engine_connector.connect_to_controller(
179 192 self.engine_service,
180 193 self.master_config.Global.furl_file,
181 194 self.master_config.Global.connect_delay,
182 195 self.master_config.Global.connect_max_tries
183 196 )
184 197
185 198 def handle_error(f):
186 199 log.msg('Error connecting to controller. This usually means that '
187 200 'i) the controller was not started, ii) a firewall was blocking '
188 201 'the engine from connecting to the controller or iii) the engine '
189 202 ' was not pointed at the right FURL file:')
190 203 log.msg(f.getErrorMessage())
191 204 reactor.callLater(0.1, reactor.stop)
192 205
193 206 d.addErrback(handle_error)
194 207
195 208 def start_mpi(self):
196 209 global mpi
197 210 mpikey = self.master_config.MPI.use
198 211 mpi_import_statement = self.master_config.MPI.get(mpikey, None)
199 212 if mpi_import_statement is not None:
200 213 try:
201 214 self.log.info("Initializing MPI:")
202 215 self.log.info(mpi_import_statement)
203 216 exec mpi_import_statement in globals()
204 217 except:
205 218 mpi = None
206 219 else:
207 220 mpi = None
208 221
209 222 def exec_lines(self):
210 223 for line in self.master_config.Global.exec_lines:
211 224 try:
212 225 log.msg("Executing statement: '%s'" % line)
213 226 self.engine_service.execute(line)
214 227 except:
215 228 log.msg("Error executing statement: %s" % line)
216 229
217 230 def start_app(self):
218 231 reactor.run()
219 232
220 233
221 234 def launch_new_instance():
222 235 """Create and run the IPython controller"""
223 236 app = IPEngineApp()
224 237 app.start()
225 238
226 239
227 240 if __name__ == '__main__':
228 241 launch_new_instance()
229 242
General Comments 0
You need to be logged in to leave comments. Login now