##// END OF EJS Templates
Work on the config system....
Brian Granger -
Show More
@@ -1,377 +1,386 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 # Unfortunately argparse by default prints help messages to stderr instead of
44 45 # stdout. This makes it annoying to capture long help screens at the command
45 46 # line, since one must know how to pipe stderr, which many users don't know how
46 47 # to do. So we override the print_help method with one that defaults to
47 48 # stdout and use our class instead.
48 49
49 50 class ArgumentParser(argparse.ArgumentParser):
50 51 """Simple argparse subclass that prints help to stdout by default."""
51 52
52 53 def print_help(self, file=None):
53 54 if file is None:
54 55 file = sys.stdout
55 56 return super(ArgumentParser, self).print_help(file)
56 57
57 58 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
58 59
59 60 #-----------------------------------------------------------------------------
60 61 # Config class for holding config information
61 62 #-----------------------------------------------------------------------------
62 63
63 64
64 65 class Config(dict):
65 66 """An attribute based dict that can do smart merges."""
66 67
67 68 def __init__(self, *args, **kwds):
68 69 dict.__init__(self, *args, **kwds)
69 70 # This sets self.__dict__ = self, but it has to be done this way
70 71 # because we are also overriding __setattr__.
71 72 dict.__setattr__(self, '__dict__', self)
72 73
73 74 def _merge(self, other):
74 75 to_update = {}
75 76 for k, v in other.items():
76 77 if not self.has_key(k):
77 78 to_update[k] = v
78 79 else: # I have this key
79 80 if isinstance(v, Config):
80 81 # Recursively merge common sub Configs
81 82 self[k]._merge(v)
82 83 else:
83 84 # Plain updates for non-Configs
84 85 to_update[k] = v
85 86
86 87 self.update(to_update)
87 88
88 89 def _is_section_key(self, key):
89 90 if key[0].upper()==key[0] and not key.startswith('_'):
90 91 return True
91 92 else:
92 93 return False
93 94
94 95 def has_key(self, key):
95 96 if self._is_section_key(key):
96 97 return True
97 98 else:
98 99 return dict.has_key(self, key)
99 100
100 101 def _has_section(self, key):
101 102 if self._is_section_key(key):
102 103 if dict.has_key(self, key):
103 104 return True
104 105 return False
105 106
106 107 def copy(self):
107 108 return type(self)(dict.copy(self))
108 109
109 110 def __copy__(self):
110 111 return self.copy()
111 112
112 113 def __deepcopy__(self, memo):
113 114 import copy
114 115 return type(self)(copy.deepcopy(self.items()))
115 116
116 117 def __getitem__(self, key):
117 118 # Because we use this for an exec namespace, we need to delegate
118 119 # the lookup of names in __builtin__ to itself. This means
119 120 # that you can't have section or attribute names that are
120 121 # builtins.
121 122 try:
122 123 return getattr(__builtin__, key)
123 124 except AttributeError:
124 125 pass
125 126 if self._is_section_key(key):
126 127 try:
127 128 return dict.__getitem__(self, key)
128 129 except KeyError:
129 130 c = Config()
130 131 dict.__setitem__(self, key, c)
131 132 return c
132 133 else:
133 134 return dict.__getitem__(self, key)
134 135
135 136 def __setitem__(self, key, value):
136 137 # Don't allow names in __builtin__ to be modified.
137 138 if hasattr(__builtin__, key):
138 139 raise ConfigError('Config variable names cannot have the same name '
139 140 'as a Python builtin: %s' % key)
140 141 if self._is_section_key(key):
141 142 if not isinstance(value, Config):
142 143 raise ValueError('values whose keys begin with an uppercase '
143 144 'char must be Config instances: %r, %r' % (key, value))
144 145 else:
145 146 dict.__setitem__(self, key, value)
146 147
147 148 def __getattr__(self, key):
148 149 try:
149 150 return self.__getitem__(key)
150 151 except KeyError, e:
151 152 raise AttributeError(e)
152 153
153 154 def __setattr__(self, key, value):
154 155 try:
155 156 self.__setitem__(key, value)
156 157 except KeyError, e:
157 158 raise AttributeError(e)
158 159
159 160 def __delattr__(self, key):
160 161 try:
161 162 dict.__delitem__(self, key)
162 163 except KeyError, e:
163 164 raise AttributeError(e)
164 165
165 166
166 167 #-----------------------------------------------------------------------------
167 168 # Config loading classes
168 169 #-----------------------------------------------------------------------------
169 170
170 171
171 172 class ConfigLoader(object):
172 173 """A object for loading configurations from just about anywhere.
173 174
174 175 The resulting configuration is packaged as a :class:`Struct`.
175 176
176 177 Notes
177 178 -----
178 179 A :class:`ConfigLoader` does one thing: load a config from a source
179 180 (file, command line arguments) and returns the data as a :class:`Struct`.
180 181 There are lots of things that :class:`ConfigLoader` does not do. It does
181 182 not implement complex logic for finding config files. It does not handle
182 183 default values or merge multiple configs. These things need to be
183 184 handled elsewhere.
184 185 """
185 186
186 187 def __init__(self):
187 188 """A base class for config loaders.
188 189
189 190 Examples
190 191 --------
191 192
192 193 >>> cl = ConfigLoader()
193 194 >>> config = cl.load_config()
194 195 >>> config
195 196 {}
196 197 """
197 198 self.clear()
198 199
199 200 def clear(self):
200 201 self.config = Config()
201 202
202 203 def load_config(self):
203 """Load a config from somewhere, return a Struct.
204 """Load a config from somewhere, return a :class:`Config` instance.
204 205
205 206 Usually, this will cause self.config to be set and then returned.
207 However, in most cases, :meth:`ConfigLoader.clear` should be called
208 to erase any previous state.
206 209 """
210 self.clear()
207 211 return self.config
208 212
209 213
210 214 class FileConfigLoader(ConfigLoader):
211 215 """A base class for file based configurations.
212 216
213 217 As we add more file based config loaders, the common logic should go
214 218 here.
215 219 """
216 220 pass
217 221
218 222
219 223 class PyFileConfigLoader(FileConfigLoader):
220 224 """A config loader for pure python files.
221 225
222 226 This calls execfile on a plain python file and looks for attributes
223 227 that are all caps. These attribute are added to the config Struct.
224 228 """
225 229
226 230 def __init__(self, filename, path=None):
227 231 """Build a config loader for a filename and path.
228 232
229 233 Parameters
230 234 ----------
231 235 filename : str
232 236 The file name of the config file.
233 237 path : str, list, tuple
234 238 The path to search for the config file on, or a sequence of
235 239 paths to try in order.
236 240 """
237 241 super(PyFileConfigLoader, self).__init__()
238 242 self.filename = filename
239 243 self.path = path
240 244 self.full_filename = ''
241 245 self.data = None
242 246
243 247 def load_config(self):
244 248 """Load the config from a file and return it as a Struct."""
249 self.clear()
245 250 self._find_file()
246 251 self._read_file_as_dict()
247 252 self._convert_to_config()
248 253 return self.config
249 254
250 255 def _find_file(self):
251 256 """Try to find the file by searching the paths."""
252 257 self.full_filename = filefind(self.filename, self.path)
253 258
254 259 def _read_file_as_dict(self):
255 260 """Load the config file into self.config, with recursive loading."""
256 261 # This closure is made available in the namespace that is used
257 262 # to exec the config file. This allows users to call
258 263 # load_subconfig('myconfig.py') to load config files recursively.
259 264 # It needs to be a closure because it has references to self.path
260 265 # and self.config. The sub-config is loaded with the same path
261 266 # as the parent, but it uses an empty config which is then merged
262 267 # with the parents.
263 268 def load_subconfig(fname):
264 269 loader = PyFileConfigLoader(fname, self.path)
265 270 try:
266 271 sub_config = loader.load_config()
267 272 except IOError:
268 273 # Pass silently if the sub config is not there. This happens
269 274 # when a user us using a profile, but not the default config.
270 275 pass
271 276 else:
272 277 self.config._merge(sub_config)
273 278
274 279 # Again, this needs to be a closure and should be used in config
275 280 # files to get the config being loaded.
276 281 def get_config():
277 282 return self.config
278 283
279 284 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
280 285 execfile(self.full_filename, namespace)
281 286
282 287 def _convert_to_config(self):
283 288 if self.data is None:
284 289 ConfigLoaderError('self.data does not exist')
285 290
286 291
287 292 class CommandLineConfigLoader(ConfigLoader):
288 293 """A config loader for command line arguments.
289 294
290 295 As we add more command line based loaders, the common logic should go
291 296 here.
292 297 """
293 298
294 299
295 class __NoConfigDefault(object): pass
296 NoConfigDefault = __NoConfigDefault()
297
298
299 300 class ArgParseConfigLoader(CommandLineConfigLoader):
300 #: Global default for arguments (see argparse docs for details)
301 argument_default = NoConfigDefault
302
303 def __init__(self, argv=None, arguments=(), *args, **kw):
304 """Create a config loader for use with argparse.
305 301
306 With the exception of ``argv`` and ``arguments``, other args and kwargs
307 arguments here are passed onto the constructor of
308 :class:`argparse.ArgumentParser`.
302 def __init__(self, argv=None, arguments=(), *parser_args, **parser_kw):
303 """Create a config loader for use with argparse.
309 304
310 305 Parameters
311 306 ----------
312 307
313 308 argv : optional, list
314 309 If given, used to read command-line arguments from, otherwise
315 310 sys.argv[1:] is used.
316 311
317 312 arguments : optional, tuple
318 Description of valid command-line arguments, to be called in sequence
319 with parser.add_argument() to configure the parser.
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 parser_args : tuple
318 A tuple of positional arguments that will be passed to the
319 constructor of :class:`argparse.ArgumentParser`.
320
321 parser_kw : dict
322 A tuple of keyword arguments that will be passed to the
323 constructor of :class:`argparse.ArgumentParser`.
320 324 """
321 325 super(CommandLineConfigLoader, self).__init__()
322 326 if argv == None:
323 327 argv = sys.argv[1:]
324 328 self.argv = argv
325 329 self.arguments = arguments
326 self.args = args
327 kwargs = dict(argument_default=self.argument_default)
328 kwargs.update(kw)
329 self.kw = kwargs
330 self.parser_args = parser_args
331 kwargs = dict(argument_default=argparse.SUPPRESS)
332 kwargs.update(parser_kw)
333 self.parser_kw = kwargs
330 334
331 335 def load_config(self, args=None):
332 336 """Parse command line arguments and return as a Struct.
333 337
334 338 Parameters
335 339 ----------
336 340
337 341 args : optional, list
338 If given, a list with the structure of sys.argv[1:] to parse arguments
339 from. If not given, the instance's self.argv attribute (given at
340 construction time) is used."""
341
342 If given, a list with the structure of sys.argv[1:] to parse
343 arguments from. If not given, the instance's self.argv attribute
344 (given at construction time) is used."""
345 self.clear()
342 346 if args is None:
343 347 args = self.argv
344 348 self._create_parser()
345 349 self._parse_args(args)
346 350 self._convert_to_config()
347 351 return self.config
348 352
349 353 def get_extra_args(self):
350 354 if hasattr(self, 'extra_args'):
351 355 return self.extra_args
352 356 else:
353 357 return []
354 358
355 359 def _create_parser(self):
356 self.parser = ArgumentParser(*self.args, **self.kw)
360 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
357 361 self._add_arguments()
358 362 self._add_other_arguments()
359 363
360 364 def _add_arguments(self):
361 365 for argument in self.arguments:
362 self.parser.add_argument(*argument[0],**argument[1])
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])
363 371
364 372 def _add_other_arguments(self):
365 373 """Meant for subclasses to add their own arguments."""
366 374 pass
367 375
368 376 def _parse_args(self, args):
369 377 """self.parser->self.parsed_data"""
370 378 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
371 379
372 380 def _convert_to_config(self):
373 381 """self.parsed_data->self.config"""
374 382 for k, v in vars(self.parsed_data).items():
375 if v is not NoConfigDefault:
376 exec_str = 'self.config.' + k + '= v'
377 exec exec_str in locals(), globals()
383 exec_str = 'self.config.' + k + '= v'
384 exec exec_str in locals(), globals()
385
386
@@ -1,162 +1,178 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 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 )
71
65 72 class TestArgParseCL(TestCase):
66 73
67 74 def test_basic(self):
68
69 arguments = (
70 (('-f','--foo'), dict(dest='Global.foo', type=str)),
71 (('-b',), dict(dest='MyClass.bar', type=int)),
72 (('-n',), dict(dest='n', action='store_true')),
73 (('Global.bam',), dict(type=str))
74 )
75 75 cl = ArgParseConfigLoader(arguments=arguments)
76 76 config = cl.load_config('-f hi -b 10 -n wow'.split())
77 77 self.assertEquals(config.Global.foo, 'hi')
78 78 self.assertEquals(config.MyClass.bar, 10)
79 79 self.assertEquals(config.n, True)
80 80 self.assertEquals(config.Global.bam, 'wow')
81 config = cl.load_config(['wow'])
82 self.assertEquals(config.keys(), ['Global'])
83 self.assertEquals(config.Global.keys(), ['bam'])
84 self.assertEquals(config.Global.bam, 'wow')
81 85
82 86 def test_add_arguments(self):
83 87
84 88 class MyLoader(ArgParseConfigLoader):
85 89 def _add_arguments(self):
86 90 subparsers = self.parser.add_subparsers(dest='subparser_name')
87 91 subparser1 = subparsers.add_parser('1')
88 92 subparser1.add_argument('-x',dest='Global.x')
89 93 subparser2 = subparsers.add_parser('2')
90 94 subparser2.add_argument('y')
91 95
92 96 cl = MyLoader()
93 97 config = cl.load_config('2 frobble'.split())
94 98 self.assertEquals(config.subparser_name, '2')
95 99 self.assertEquals(config.y, 'frobble')
96 100 config = cl.load_config('1 -x frobble'.split())
97 101 self.assertEquals(config.subparser_name, '1')
98 102 self.assertEquals(config.Global.x, 'frobble')
99 103
104 def test_argv(self):
105 cl = ArgParseConfigLoader(
106 argv='-f hi -b 10 -n wow'.split(),
107 arguments=arguments
108 )
109 config = cl.load_config()
110 self.assertEquals(config.Global.foo, 'hi')
111 self.assertEquals(config.MyClass.bar, 10)
112 self.assertEquals(config.n, True)
113 self.assertEquals(config.Global.bam, 'wow')
114
115
100 116 class TestConfig(TestCase):
101 117
102 118 def test_setget(self):
103 119 c = Config()
104 120 c.a = 10
105 121 self.assertEquals(c.a, 10)
106 122 self.assertEquals(c.has_key('b'), False)
107 123
108 124 def test_auto_section(self):
109 125 c = Config()
110 126 self.assertEquals(c.has_key('A'), True)
111 127 self.assertEquals(c._has_section('A'), False)
112 128 A = c.A
113 129 A.foo = 'hi there'
114 130 self.assertEquals(c._has_section('A'), True)
115 131 self.assertEquals(c.A.foo, 'hi there')
116 132 del c.A
117 133 self.assertEquals(len(c.A.keys()),0)
118 134
119 135 def test_merge_doesnt_exist(self):
120 136 c1 = Config()
121 137 c2 = Config()
122 138 c2.bar = 10
123 139 c2.Foo.bar = 10
124 140 c1._merge(c2)
125 141 self.assertEquals(c1.Foo.bar, 10)
126 142 self.assertEquals(c1.bar, 10)
127 143 c2.Bar.bar = 10
128 144 c1._merge(c2)
129 145 self.assertEquals(c1.Bar.bar, 10)
130 146
131 147 def test_merge_exists(self):
132 148 c1 = Config()
133 149 c2 = Config()
134 150 c1.Foo.bar = 10
135 151 c1.Foo.bam = 30
136 152 c2.Foo.bar = 20
137 153 c2.Foo.wow = 40
138 154 c1._merge(c2)
139 155 self.assertEquals(c1.Foo.bam, 30)
140 156 self.assertEquals(c1.Foo.bar, 20)
141 157 self.assertEquals(c1.Foo.wow, 40)
142 158 c2.Foo.Bam.bam = 10
143 159 c1._merge(c2)
144 160 self.assertEquals(c1.Foo.Bam.bam, 10)
145 161
146 162 def test_deepcopy(self):
147 163 c1 = Config()
148 164 c1.Foo.bar = 10
149 165 c1.Foo.bam = 30
150 166 c1.a = 'asdf'
151 167 c1.b = range(10)
152 168 import copy
153 169 c2 = copy.deepcopy(c1)
154 170 self.assertEquals(c1, c2)
155 171 self.assert_(c1 is not c2)
156 172 self.assert_(c1.Foo is not c2.Foo)
157 173
158 174 def test_builtin(self):
159 175 c1 = Config()
160 176 exec 'foo = True' in c1
161 177 self.assertEquals(c1.foo, True)
162 178 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
@@ -1,649 +1,648 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 31 from IPython.core.application import Application
32 32 from IPython.core.iplib import InteractiveShell
33 33 from IPython.config.loader import (
34 34 Config,
35 PyFileConfigLoader,
36 # NoConfigDefault,
35 PyFileConfigLoader
37 36 )
38 37 from IPython.lib import inputhook
39 38 from IPython.utils.path import filefind, get_ipython_dir
40 39 from . import usage
41 40
42 41 #-----------------------------------------------------------------------------
43 42 # Globals, utilities and helpers
44 43 #-----------------------------------------------------------------------------
45 44
46 45 default_config_file_name = u'ipython_config.py'
47 46
48 47 cl_args = (
49 48 (('--autocall',), dict(
50 49 type=int, dest='InteractiveShell.autocall',
51 50 help=
52 51 """Make IPython automatically call any callable object even if you
53 52 didn't type explicit parentheses. For example, 'str 43' becomes
54 53 'str(43)' automatically. The value can be '0' to disable the feature,
55 54 '1' for 'smart' autocall, where it is not applied if there are no more
56 55 arguments on the line, and '2' for 'full' autocall, where all callable
57 56 objects are automatically called (even if no arguments are present).
58 57 The default is '1'.""",
59 58 metavar='InteractiveShell.autocall')
60 59 ),
61 60 (('--autoindent',), dict(
62 61 action='store_true', dest='InteractiveShell.autoindent',
63 62 help='Turn on autoindenting.')
64 63 ),
65 64 (('--no-autoindent',), dict(
66 65 action='store_false', dest='InteractiveShell.autoindent',
67 66 help='Turn off autoindenting.')
68 67 ),
69 68 (('--automagic',), dict(
70 69 action='store_true', dest='InteractiveShell.automagic',
71 70 help='Turn on the auto calling of magic commands.'
72 71 'Type %%magic at the IPython prompt for more information.')
73 72 ),
74 73 (('--no-automagic',), dict(
75 74 action='store_false', dest='InteractiveShell.automagic',
76 75 help='Turn off the auto calling of magic commands.')
77 76 ),
78 77 (('--autoedit-syntax',), dict(
79 78 action='store_true', dest='InteractiveShell.autoedit_syntax',
80 79 help='Turn on auto editing of files with syntax errors.')
81 80 ),
82 81 (('--no-autoedit-syntax',), dict(
83 82 action='store_false', dest='InteractiveShell.autoedit_syntax',
84 83 help='Turn off auto editing of files with syntax errors.')
85 84 ),
86 85 (('--banner',), dict(
87 86 action='store_true', dest='Global.display_banner',
88 87 help='Display a banner upon starting IPython.')
89 88 ),
90 89 (('--no-banner',), dict(
91 90 action='store_false', dest='Global.display_banner',
92 91 help="Don't display a banner upon starting IPython.")
93 92 ),
94 93 (('--cache-size',), dict(
95 94 type=int, dest='InteractiveShell.cache_size',
96 95 help=
97 96 """Set the size of the output cache. The default is 1000, you can
98 97 change it permanently in your config file. Setting it to 0 completely
99 98 disables the caching system, and the minimum value accepted is 20 (if
100 99 you provide a value less than 20, it is reset to 0 and a warning is
101 100 issued). This limit is defined because otherwise you'll spend more
102 101 time re-flushing a too small cache than working.
103 102 """,
104 103 metavar='InteractiveShell.cache_size')
105 104 ),
106 105 (('--classic',), dict(
107 106 action='store_true', dest='Global.classic',
108 107 help="Gives IPython a similar feel to the classic Python prompt.")
109 108 ),
110 109 (('--colors',), dict(
111 110 type=str, dest='InteractiveShell.colors',
112 111 help="Set the color scheme (NoColor, Linux, and LightBG).",
113 112 metavar='InteractiveShell.colors')
114 113 ),
115 114 (('--color-info',), dict(
116 115 action='store_true', dest='InteractiveShell.color_info',
117 116 help=
118 117 """IPython can display information about objects via a set of func-
119 118 tions, and optionally can use colors for this, syntax highlighting
120 119 source code and various other elements. However, because this
121 120 information is passed through a pager (like 'less') and many pagers get
122 121 confused with color codes, this option is off by default. You can test
123 122 it and turn it on permanently in your ipython_config.py file if it
124 123 works for you. Test it and turn it on permanently if it works with
125 124 your system. The magic function %%color_info allows you to toggle this
126 125 inter- actively for testing."""
127 126 )
128 127 ),
129 128 (('--no-color-info',), dict(
130 129 action='store_false', dest='InteractiveShell.color_info',
131 130 help="Disable using colors for info related things.")
132 131 ),
133 132 (('--confirm-exit',), dict(
134 133 action='store_true', dest='InteractiveShell.confirm_exit',
135 134 help=
136 135 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 136 in Unix, Control-Z/Enter in Windows). By typing 'exit', 'quit' or
138 137 '%%Exit', you can force a direct exit without any confirmation.
139 138 """
140 139 )
141 140 ),
142 141 (('--no-confirm-exit',), dict(
143 142 action='store_false', dest='InteractiveShell.confirm_exit',
144 143 help="Don't prompt the user when exiting.")
145 144 ),
146 145 (('--deep-reload',), dict(
147 146 action='store_true', dest='InteractiveShell.deep_reload',
148 147 help=
149 148 """Enable deep (recursive) reloading by default. IPython can use the
150 149 deep_reload module which reloads changes in modules recursively (it
151 150 replaces the reload() function, so you don't need to change anything to
152 151 use it). deep_reload() forces a full reload of modules whose code may
153 152 have changed, which the default reload() function does not. When
154 153 deep_reload is off, IPython will use the normal reload(), but
155 154 deep_reload will still be available as dreload(). This fea- ture is off
156 155 by default [which means that you have both normal reload() and
157 156 dreload()].""")
158 157 ),
159 158 (('--no-deep-reload',), dict(
160 159 action='store_false', dest='InteractiveShell.deep_reload',
161 160 help="Disable deep (recursive) reloading by default.")
162 161 ),
163 162 (('--editor',), dict(
164 163 type=str, dest='InteractiveShell.editor',
165 164 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
166 165 metavar='InteractiveShell.editor')
167 166 ),
168 167 (('--log','-l'), dict(
169 168 action='store_true', dest='InteractiveShell.logstart',
170 169 help="Start logging to the default log file (./ipython_log.py).")
171 170 ),
172 171 (('--logfile','-lf'), dict(
173 172 type=unicode, dest='InteractiveShell.logfile',
174 173 help="Start logging to logfile with this name.",
175 174 metavar='InteractiveShell.logfile')
176 175 ),
177 176 (('--log-append','-la'), dict(
178 177 type=unicode, dest='InteractiveShell.logappend',
179 178 help="Start logging to the given file in append mode.",
180 179 metavar='InteractiveShell.logfile')
181 180 ),
182 181 (('--pdb',), dict(
183 182 action='store_true', dest='InteractiveShell.pdb',
184 183 help="Enable auto calling the pdb debugger after every exception.")
185 184 ),
186 185 (('--no-pdb',), dict(
187 186 action='store_false', dest='InteractiveShell.pdb',
188 187 help="Disable auto calling the pdb debugger after every exception.")
189 188 ),
190 189 (('--pprint',), dict(
191 190 action='store_true', dest='InteractiveShell.pprint',
192 191 help="Enable auto pretty printing of results.")
193 192 ),
194 193 (('--no-pprint',), dict(
195 194 action='store_false', dest='InteractiveShell.pprint',
196 195 help="Disable auto auto pretty printing of results.")
197 196 ),
198 197 (('--prompt-in1','-pi1'), dict(
199 198 type=str, dest='InteractiveShell.prompt_in1',
200 199 help=
201 200 """Set the main input prompt ('In [\#]: '). Note that if you are using
202 201 numbered prompts, the number is represented with a '\#' in the string.
203 202 Don't forget to quote strings with spaces embedded in them. Most
204 203 bash-like escapes can be used to customize IPython's prompts, as well
205 204 as a few additional ones which are IPython-spe- cific. All valid
206 205 prompt escapes are described in detail in the Customization section of
207 206 the IPython manual.""",
208 207 metavar='InteractiveShell.prompt_in1')
209 208 ),
210 209 (('--prompt-in2','-pi2'), dict(
211 210 type=str, dest='InteractiveShell.prompt_in2',
212 211 help=
213 212 """Set the secondary input prompt (' .\D.: '). Similar to the previous
214 213 option, but used for the continuation prompts. The special sequence
215 214 '\D' is similar to '\#', but with all digits replaced by dots (so you
216 215 can have your continuation prompt aligned with your input prompt).
217 216 Default: ' .\D.: ' (note three spaces at the start for alignment with
218 217 'In [\#]')""",
219 218 metavar='InteractiveShell.prompt_in2')
220 219 ),
221 220 (('--prompt-out','-po'), dict(
222 221 type=str, dest='InteractiveShell.prompt_out',
223 222 help="Set the output prompt ('Out[\#]:')",
224 223 metavar='InteractiveShell.prompt_out')
225 224 ),
226 225 (('--quick',), dict(
227 226 action='store_true', dest='Global.quick',
228 227 help="Enable quick startup with no config files.")
229 228 ),
230 229 (('--readline',), dict(
231 230 action='store_true', dest='InteractiveShell.readline_use',
232 231 help="Enable readline for command line usage.")
233 232 ),
234 233 (('--no-readline',), dict(
235 234 action='store_false', dest='InteractiveShell.readline_use',
236 235 help="Disable readline for command line usage.")
237 236 ),
238 237 (('--screen-length','-sl'), dict(
239 238 type=int, dest='InteractiveShell.screen_length',
240 239 help=
241 240 """Number of lines of your screen, used to control printing of very
242 241 long strings. Strings longer than this number of lines will be sent
243 242 through a pager instead of directly printed. The default value for
244 243 this is 0, which means IPython will auto-detect your screen size every
245 244 time it needs to print certain potentially long strings (this doesn't
246 245 change the behavior of the 'print' keyword, it's only triggered
247 246 internally). If for some reason this isn't working well (it needs
248 247 curses support), specify it yourself. Otherwise don't change the
249 248 default.""",
250 249 metavar='InteractiveShell.screen_length')
251 250 ),
252 251 (('--separate-in','-si'), dict(
253 252 type=str, dest='InteractiveShell.separate_in',
254 253 help="Separator before input prompts. Default '\\n'.",
255 254 metavar='InteractiveShell.separate_in')
256 255 ),
257 256 (('--separate-out','-so'), dict(
258 257 type=str, dest='InteractiveShell.separate_out',
259 258 help="Separator before output prompts. Default 0 (nothing).",
260 259 metavar='InteractiveShell.separate_out')
261 260 ),
262 261 (('--separate-out2','-so2'), dict(
263 262 type=str, dest='InteractiveShell.separate_out2',
264 263 help="Separator after output prompts. Default 0 (nonight).",
265 264 metavar='InteractiveShell.separate_out2')
266 265 ),
267 266 (('-no-sep',), dict(
268 267 action='store_true', dest='Global.nosep',
269 268 help="Eliminate all spacing between prompts.")
270 269 ),
271 270 (('--term-title',), dict(
272 271 action='store_true', dest='InteractiveShell.term_title',
273 272 help="Enable auto setting the terminal title.")
274 273 ),
275 274 (('--no-term-title',), dict(
276 275 action='store_false', dest='InteractiveShell.term_title',
277 276 help="Disable auto setting the terminal title.")
278 277 ),
279 278 (('--xmode',), dict(
280 279 type=str, dest='InteractiveShell.xmode',
281 280 help=
282 281 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
283 282 similar to python's normal traceback printing. Context: prints 5 lines
284 283 of context source code around each line in the traceback. Verbose:
285 284 similar to Context, but additionally prints the variables currently
286 285 visible where the exception happened (shortening their strings if too
287 286 long). This can potentially be very slow, if you happen to have a huge
288 287 data structure whose string representation is complex to compute.
289 288 Your computer may appear to freeze for a while with cpu usage at 100%%.
290 289 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
291 290 it more than once).
292 291 """,
293 292 metavar='InteractiveShell.xmode')
294 293 ),
295 294 (('--ext',), dict(
296 295 type=str, dest='Global.extra_extension',
297 296 help="The dotted module name of an IPython extension to load.",
298 297 metavar='Global.extra_extension')
299 298 ),
300 299 (('-c',), dict(
301 300 type=str, dest='Global.code_to_run',
302 301 help="Execute the given command string.",
303 302 metavar='Global.code_to_run')
304 303 ),
305 304 (('-i',), dict(
306 305 action='store_true', dest='Global.force_interact',
307 306 help=
308 307 "If running code from the command line, become interactive afterwards."
309 308 )
310 309 ),
311 310
312 311 # Options to start with GUI control enabled from the beginning
313 312 (('--gui',), dict(
314 313 type=str, dest='Global.gui',
315 314 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
316 315 metavar='gui-mode')
317 316 ),
318 317
319 318 (('--pylab','-pylab'), dict(
320 319 type=str, dest='Global.pylab',
321 320 nargs='?', const='auto', metavar='gui-mode',
322 321 help="Pre-load matplotlib and numpy for interactive use. "+
323 322 "If no value is given, the gui backend is matplotlib's, else use "+
324 323 "one of: ['tk', 'qt', 'wx', 'gtk'].")
325 324 ),
326 325
327 326 # Legacy GUI options. Leave them in for backwards compatibility, but the
328 327 # 'thread' names are really a misnomer now.
329 328 (('--wthread','-wthread'), dict(
330 329 action='store_true', dest='Global.wthread',
331 330 help="Enable wxPython event loop integration "+
332 331 "(DEPRECATED, use --gui wx)")
333 332 ),
334 333 (('--q4thread','--qthread','-q4thread','-qthread'), dict(
335 334 action='store_true', dest='Global.q4thread',
336 335 help="Enable Qt4 event loop integration. Qt3 is no longer supported. "+
337 336 "(DEPRECATED, use --gui qt)")
338 337 ),
339 338 (('--gthread','-gthread'), dict(
340 339 action='store_true', dest='Global.gthread',
341 340 help="Enable GTK event loop integration. "+
342 341 "(DEPRECATED, use --gui gtk)")
343 342 ),
344 343 )
345 344
346 345 #-----------------------------------------------------------------------------
347 346 # Main classes and functions
348 347 #-----------------------------------------------------------------------------
349 348
350 349 class IPythonApp(Application):
351 350 name = u'ipython'
352 351 #: argparse formats better the 'usage' than the 'description' field
353 352 description = None
354 353 #: usage message printed by argparse. If None, auto-generate
355 354 usage = usage.cl_usage
356 355
357 356 config_file_name = default_config_file_name
358 357
359 358 cl_arguments = Application.cl_arguments + cl_args
360 359
361 360 # Private and configuration attributes
362 361 _CrashHandler = crashhandler.IPythonCrashHandler
363 362
364 363 def __init__(self, argv=None,
365 364 constructor_config=None, override_config=None,
366 365 **shell_params):
367 366 """Create a new IPythonApp.
368 367
369 368 See the parent class for details on how configuration is handled.
370 369
371 370 Parameters
372 371 ----------
373 372 argv : optional, list
374 373 If given, used as the command-line argv environment to read arguments
375 374 from.
376 375
377 376 constructor_config : optional, Config
378 377 If given, additional config that is merged last, after internal
379 378 defaults, command-line and file-based configs.
380 379
381 380 override_config : optional, Config
382 381 If given, config that overrides all others unconditionally (except
383 382 for internal defaults, which ensure that all parameters exist).
384 383
385 384 shell_params : optional, dict
386 385 All other keywords are passed to the :class:`iplib.InteractiveShell`
387 386 constructor.
388 387 """
389 388 super(IPythonApp, self).__init__(argv, constructor_config,
390 389 override_config)
391 390 self.shell_params = shell_params
392 391
393 392 def create_default_config(self):
394 393 super(IPythonApp, self).create_default_config()
395 394 # Eliminate multiple lookups
396 395 Global = self.default_config.Global
397 396
398 397 # Set all default values
399 398 Global.display_banner = True
400 399
401 400 # If the -c flag is given or a file is given to run at the cmd line
402 401 # like "ipython foo.py", normally we exit without starting the main
403 402 # loop. The force_interact config variable allows a user to override
404 403 # this and interact. It is also set by the -i cmd line flag, just
405 404 # like Python.
406 405 Global.force_interact = False
407 406
408 407 # By default always interact by starting the IPython mainloop.
409 408 Global.interact = True
410 409
411 410 # No GUI integration by default
412 411 Global.gui = False
413 412 # Pylab off by default
414 413 Global.pylab = False
415 414
416 415 # Deprecated versions of gui support that used threading, we support
417 416 # them just for bacwards compatibility as an alternate spelling for
418 417 # '--gui X'
419 418 Global.qthread = False
420 419 Global.q4thread = False
421 420 Global.wthread = False
422 421 Global.gthread = False
423 422
424 423 def load_file_config(self):
425 424 if hasattr(self.command_line_config.Global, 'quick'):
426 425 if self.command_line_config.Global.quick:
427 426 self.file_config = Config()
428 427 return
429 428 super(IPythonApp, self).load_file_config()
430 429
431 430 def post_load_file_config(self):
432 431 if hasattr(self.command_line_config.Global, 'extra_extension'):
433 432 if not hasattr(self.file_config.Global, 'extensions'):
434 433 self.file_config.Global.extensions = []
435 434 self.file_config.Global.extensions.append(
436 435 self.command_line_config.Global.extra_extension)
437 436 del self.command_line_config.Global.extra_extension
438 437
439 438 def pre_construct(self):
440 439 config = self.master_config
441 440
442 441 if hasattr(config.Global, 'classic'):
443 442 if config.Global.classic:
444 443 config.InteractiveShell.cache_size = 0
445 444 config.InteractiveShell.pprint = 0
446 445 config.InteractiveShell.prompt_in1 = '>>> '
447 446 config.InteractiveShell.prompt_in2 = '... '
448 447 config.InteractiveShell.prompt_out = ''
449 448 config.InteractiveShell.separate_in = \
450 449 config.InteractiveShell.separate_out = \
451 450 config.InteractiveShell.separate_out2 = ''
452 451 config.InteractiveShell.colors = 'NoColor'
453 452 config.InteractiveShell.xmode = 'Plain'
454 453
455 454 if hasattr(config.Global, 'nosep'):
456 455 if config.Global.nosep:
457 456 config.InteractiveShell.separate_in = \
458 457 config.InteractiveShell.separate_out = \
459 458 config.InteractiveShell.separate_out2 = ''
460 459
461 460 # if there is code of files to run from the cmd line, don't interact
462 461 # unless the -i flag (Global.force_interact) is true.
463 462 code_to_run = config.Global.get('code_to_run','')
464 463 file_to_run = False
465 464 if self.extra_args and self.extra_args[0]:
466 465 file_to_run = True
467 466 if file_to_run or code_to_run:
468 467 if not config.Global.force_interact:
469 468 config.Global.interact = False
470 469
471 470 def construct(self):
472 471 # I am a little hesitant to put these into InteractiveShell itself.
473 472 # But that might be the place for them
474 473 sys.path.insert(0, '')
475 474
476 475 # Create an InteractiveShell instance
477 476 self.shell = InteractiveShell(None, self.master_config,
478 477 **self.shell_params )
479 478
480 479 def post_construct(self):
481 480 """Do actions after construct, but before starting the app."""
482 481 config = self.master_config
483 482
484 483 # shell.display_banner should always be False for the terminal
485 484 # based app, because we call shell.show_banner() by hand below
486 485 # so the banner shows *before* all extension loading stuff.
487 486 self.shell.display_banner = False
488 487
489 488 if config.Global.display_banner and \
490 489 config.Global.interact:
491 490 self.shell.show_banner()
492 491
493 492 # Make sure there is a space below the banner.
494 493 if self.log_level <= logging.INFO: print
495 494
496 495 # Now a variety of things that happen after the banner is printed.
497 496 self._enable_gui_pylab()
498 497 self._load_extensions()
499 498 self._run_exec_lines()
500 499 self._run_exec_files()
501 500 self._run_cmd_line_code()
502 501
503 502 def _enable_gui_pylab(self):
504 503 """Enable GUI event loop integration, taking pylab into account."""
505 504 Global = self.master_config.Global
506 505
507 506 # Select which gui to use
508 507 if Global.gui:
509 508 gui = Global.gui
510 509 # The following are deprecated, but there's likely to be a lot of use
511 510 # of this form out there, so we might as well support it for now. But
512 511 # the --gui option above takes precedence.
513 512 elif Global.wthread:
514 513 gui = inputhook.GUI_WX
515 514 elif Global.qthread:
516 515 gui = inputhook.GUI_QT
517 516 elif Global.gthread:
518 517 gui = inputhook.GUI_GTK
519 518 else:
520 519 gui = None
521 520
522 521 # Using --pylab will also require gui activation, though which toolkit
523 522 # to use may be chosen automatically based on mpl configuration.
524 523 if Global.pylab:
525 524 activate = self.shell.enable_pylab
526 525 if Global.pylab == 'auto':
527 526 gui = None
528 527 else:
529 528 gui = Global.pylab
530 529 else:
531 530 # Enable only GUI integration, no pylab
532 531 activate = inputhook.enable_gui
533 532
534 533 if gui or Global.pylab:
535 534 try:
536 535 self.log.info("Enabling GUI event loop integration, "
537 536 "toolkit=%s, pylab=%s" % (gui, Global.pylab) )
538 537 activate(gui)
539 538 except:
540 539 self.log.warn("Error in enabling GUI event loop integration:")
541 540 self.shell.showtraceback()
542 541
543 542 def _load_extensions(self):
544 543 """Load all IPython extensions in Global.extensions.
545 544
546 545 This uses the :meth:`InteractiveShell.load_extensions` to load all
547 546 the extensions listed in ``self.master_config.Global.extensions``.
548 547 """
549 548 try:
550 549 if hasattr(self.master_config.Global, 'extensions'):
551 550 self.log.debug("Loading IPython extensions...")
552 551 extensions = self.master_config.Global.extensions
553 552 for ext in extensions:
554 553 try:
555 554 self.log.info("Loading IPython extension: %s" % ext)
556 555 self.shell.load_extension(ext)
557 556 except:
558 557 self.log.warn("Error in loading extension: %s" % ext)
559 558 self.shell.showtraceback()
560 559 except:
561 560 self.log.warn("Unknown error in loading extensions:")
562 561 self.shell.showtraceback()
563 562
564 563 def _run_exec_lines(self):
565 564 """Run lines of code in Global.exec_lines in the user's namespace."""
566 565 try:
567 566 if hasattr(self.master_config.Global, 'exec_lines'):
568 567 self.log.debug("Running code from Global.exec_lines...")
569 568 exec_lines = self.master_config.Global.exec_lines
570 569 for line in exec_lines:
571 570 try:
572 571 self.log.info("Running code in user namespace: %s" % line)
573 572 self.shell.runlines(line)
574 573 except:
575 574 self.log.warn("Error in executing line in user namespace: %s" % line)
576 575 self.shell.showtraceback()
577 576 except:
578 577 self.log.warn("Unknown error in handling Global.exec_lines:")
579 578 self.shell.showtraceback()
580 579
581 580 def _exec_file(self, fname):
582 581 full_filename = filefind(fname, [u'.', self.ipython_dir])
583 582 if os.path.isfile(full_filename):
584 583 if full_filename.endswith(u'.py'):
585 584 self.log.info("Running file in user namespace: %s" % full_filename)
586 585 self.shell.safe_execfile(full_filename, self.shell.user_ns)
587 586 elif full_filename.endswith('.ipy'):
588 587 self.log.info("Running file in user namespace: %s" % full_filename)
589 588 self.shell.safe_execfile_ipy(full_filename)
590 589 else:
591 590 self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename)
592 591
593 592 def _run_exec_files(self):
594 593 try:
595 594 if hasattr(self.master_config.Global, 'exec_files'):
596 595 self.log.debug("Running files in Global.exec_files...")
597 596 exec_files = self.master_config.Global.exec_files
598 597 for fname in exec_files:
599 598 self._exec_file(fname)
600 599 except:
601 600 self.log.warn("Unknown error in handling Global.exec_files:")
602 601 self.shell.showtraceback()
603 602
604 603 def _run_cmd_line_code(self):
605 604 if hasattr(self.master_config.Global, 'code_to_run'):
606 605 line = self.master_config.Global.code_to_run
607 606 try:
608 607 self.log.info("Running code given at command line (-c): %s" % line)
609 608 self.shell.runlines(line)
610 609 except:
611 610 self.log.warn("Error in executing line in user namespace: %s" % line)
612 611 self.shell.showtraceback()
613 612 return
614 613 # Like Python itself, ignore the second if the first of these is present
615 614 try:
616 615 fname = self.extra_args[0]
617 616 except:
618 617 pass
619 618 else:
620 619 try:
621 620 self._exec_file(fname)
622 621 except:
623 622 self.log.warn("Error in executing file in user namespace: %s" % fname)
624 623 self.shell.showtraceback()
625 624
626 625 def start_app(self):
627 626 if self.master_config.Global.interact:
628 627 self.log.debug("Starting IPython's mainloop...")
629 628 self.shell.mainloop()
630 629 else:
631 630 self.log.debug("IPython not interactive, start_app is no-op...")
632 631
633 632
634 633 def load_default_config(ipython_dir=None):
635 634 """Load the default config file from the default ipython_dir.
636 635
637 636 This is useful for embedded shells.
638 637 """
639 638 if ipython_dir is None:
640 639 ipython_dir = get_ipython_dir()
641 640 cl = PyFileConfigLoader(default_config_file_name, ipython_dir)
642 641 config = cl.load_config()
643 642 return config
644 643
645 644
646 645 def launch_new_instance():
647 646 """Create and run a full blown IPython instance"""
648 647 app = IPythonApp()
649 648 app.start()
@@ -1,463 +1,462 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 25 from IPython.core import release
26 from IPython.external.argparse import ArgumentParser
27 from IPython.config.loader import ArgParseConfigLoader, NoConfigDefault
26 from IPython.external.argparse import ArgumentParser, SUPPRESS
27 from IPython.config.loader import ArgParseConfigLoader
28 28 from IPython.utils.importstring import import_item
29 29
30 30 from IPython.kernel.clusterdir import (
31 31 ApplicationWithClusterDir, ClusterDirError, PIDFileError
32 32 )
33 33
34 34 from twisted.internet import reactor, defer
35 35 from twisted.python import log, failure
36 36
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # The ipcluster application
40 40 #-----------------------------------------------------------------------------
41 41
42 42
43 43 # Exit codes for ipcluster
44 44
45 45 # This will be the exit code if the ipcluster appears to be running because
46 46 # a .pid file exists
47 47 ALREADY_STARTED = 10
48 48
49 49 # This will be the exit code if ipcluster stop is run, but there is not .pid
50 50 # file to be found.
51 51 ALREADY_STOPPED = 11
52 52
53 53
54 54 class IPClusterCLLoader(ArgParseConfigLoader):
55 55
56 56 def _add_other_arguments(self):
57 57 # This has all the common options that all subcommands use
58 58 parent_parser1 = ArgumentParser(add_help=False,
59 argument_default=NoConfigDefault)
59 argument_default=SUPPRESS)
60 60 parent_parser1.add_argument('--ipython-dir',
61 61 dest='Global.ipython_dir',type=unicode,
62 62 help='Set to override default location of Global.ipython_dir.',
63 63 metavar='Global.ipython_dir')
64 64 parent_parser1.add_argument('--log-level',
65 65 dest="Global.log_level",type=int,
66 66 help='Set the log level (0,10,20,30,40,50). Default is 30.',
67 67 metavar='Global.log_level')
68 68
69 69 # This has all the common options that other subcommands use
70 70 parent_parser2 = ArgumentParser(add_help=False,
71 argument_default=NoConfigDefault)
71 argument_default=SUPPRESS)
72 72 parent_parser2.add_argument('-p','--profile',
73 73 dest='Global.profile',type=unicode,
74 74 help='The string name of the profile to be used. This determines '
75 75 'the name of the cluster dir as: cluster_<profile>. The default profile '
76 76 'is named "default". The cluster directory is resolve this way '
77 77 'if the --cluster-dir option is not used.',
78 78 metavar='Global.profile')
79 79 parent_parser2.add_argument('--cluster-dir',
80 80 dest='Global.cluster_dir',type=unicode,
81 81 help='Set the cluster dir. This overrides the logic used by the '
82 82 '--profile option.',
83 83 metavar='Global.cluster_dir'),
84 84 parent_parser2.add_argument('--work-dir',
85 85 dest='Global.work_dir',type=unicode,
86 86 help='Set the working dir for the process.',
87 87 metavar='Global.work_dir')
88 88 parent_parser2.add_argument('--log-to-file',
89 89 action='store_true', dest='Global.log_to_file',
90 90 help='Log to a file in the log directory (default is stdout)'
91 91 )
92 92
93 93 subparsers = self.parser.add_subparsers(
94 94 dest='Global.subcommand',
95 95 title='ipcluster subcommands',
96 96 description='ipcluster has a variety of subcommands. '
97 97 'The general way of running ipcluster is "ipcluster <cmd> '
98 98 ' [options]""',
99 99 help='For more help, type "ipcluster <cmd> -h"')
100 100
101 101 parser_list = subparsers.add_parser(
102 102 'list',
103 103 help='List all clusters in cwd and ipython_dir.',
104 104 parents=[parent_parser1]
105 105 )
106 106
107 107 parser_create = subparsers.add_parser(
108 108 'create',
109 109 help='Create a new cluster directory.',
110 110 parents=[parent_parser1, parent_parser2]
111 111 )
112 112 parser_create.add_argument(
113 113 '--reset-config',
114 114 dest='Global.reset_config', action='store_true',
115 default=NoConfigDefault,
116 115 help='Recopy the default config files to the cluster directory. '
117 116 'You will loose any modifications you have made to these files.'
118 117 )
119 118
120 119 parser_start = subparsers.add_parser(
121 120 'start',
122 121 help='Start a cluster.',
123 122 parents=[parent_parser1, parent_parser2]
124 123 )
125 124 parser_start.add_argument(
126 125 '-n', '--number',
127 126 type=int, dest='Global.n',
128 127 help='The number of engines to start.',
129 128 metavar='Global.n'
130 129 )
131 130 parser_start.add_argument('--clean-logs',
132 131 dest='Global.clean_logs', action='store_true',
133 132 help='Delete old log flies before starting.',
134 133 )
135 134 parser_start.add_argument('--no-clean-logs',
136 135 dest='Global.clean_logs', action='store_false',
137 136 help="Don't delete old log flies before starting.",
138 137 )
139 138 parser_start.add_argument('--daemon',
140 139 dest='Global.daemonize', action='store_true',
141 140 help='Daemonize the ipcluster program. This implies --log-to-file',
142 141 )
143 142 parser_start.add_argument('--no-daemon',
144 143 dest='Global.daemonize', action='store_false',
145 144 help="Dont't daemonize the ipcluster program.",
146 145 )
147 146
148 147 parser_start = subparsers.add_parser(
149 148 'stop',
150 149 help='Stop a cluster.',
151 150 parents=[parent_parser1, parent_parser2]
152 151 )
153 152 parser_start.add_argument('--signal',
154 153 dest='Global.signal', type=int,
155 154 help="The signal number to use in stopping the cluster (default=2).",
156 155 metavar="Global.signal",
157 156 )
158 157
159 158
160 159 default_config_file_name = u'ipcluster_config.py'
161 160
162 161
163 162 _description = """Start an IPython cluster for parallel computing.\n\n
164 163
165 164 An IPython cluster consists of 1 controller and 1 or more engines.
166 165 This command automates the startup of these processes using a wide
167 166 range of startup methods (SSH, local processes, PBS, mpiexec,
168 167 Windows HPC Server 2008). To start a cluster with 4 engines on your
169 168 local host simply do "ipcluster start -n 4". For more complex usage
170 169 you will typically do "ipcluster create -p mycluster", then edit
171 170 configuration files, followed by "ipcluster start -p mycluster -n 4".
172 171 """
173 172
174 173
175 174 class IPClusterApp(ApplicationWithClusterDir):
176 175
177 176 name = u'ipcluster'
178 177 description = _description
179 178 config_file_name = default_config_file_name
180 179 default_log_level = logging.INFO
181 180 auto_create_cluster_dir = False
182 181
183 182 def create_default_config(self):
184 183 super(IPClusterApp, self).create_default_config()
185 184 self.default_config.Global.controller_launcher = \
186 185 'IPython.kernel.launcher.LocalControllerLauncher'
187 186 self.default_config.Global.engine_launcher = \
188 187 'IPython.kernel.launcher.LocalEngineSetLauncher'
189 188 self.default_config.Global.n = 2
190 189 self.default_config.Global.reset_config = False
191 190 self.default_config.Global.clean_logs = True
192 191 self.default_config.Global.signal = 2
193 192 self.default_config.Global.daemonize = False
194 193
195 194 def create_command_line_config(self):
196 195 """Create and return a command line config loader."""
197 196 return IPClusterCLLoader(
198 197 description=self.description,
199 198 version=release.version
200 199 )
201 200
202 201 def find_resources(self):
203 202 subcommand = self.command_line_config.Global.subcommand
204 203 if subcommand=='list':
205 204 self.list_cluster_dirs()
206 205 # Exit immediately because there is nothing left to do.
207 206 self.exit()
208 207 elif subcommand=='create':
209 208 self.auto_create_cluster_dir = True
210 209 super(IPClusterApp, self).find_resources()
211 210 elif subcommand=='start' or subcommand=='stop':
212 211 self.auto_create_cluster_dir = True
213 212 try:
214 213 super(IPClusterApp, self).find_resources()
215 214 except ClusterDirError:
216 215 raise ClusterDirError(
217 216 "Could not find a cluster directory. A cluster dir must "
218 217 "be created before running 'ipcluster start'. Do "
219 218 "'ipcluster create -h' or 'ipcluster list -h' for more "
220 219 "information about creating and listing cluster dirs."
221 220 )
222 221
223 222 def list_cluster_dirs(self):
224 223 # Find the search paths
225 224 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
226 225 if cluster_dir_paths:
227 226 cluster_dir_paths = cluster_dir_paths.split(':')
228 227 else:
229 228 cluster_dir_paths = []
230 229 try:
231 230 ipython_dir = self.command_line_config.Global.ipython_dir
232 231 except AttributeError:
233 232 ipython_dir = self.default_config.Global.ipython_dir
234 233 paths = [os.getcwd(), ipython_dir] + \
235 234 cluster_dir_paths
236 235 paths = list(set(paths))
237 236
238 237 self.log.info('Searching for cluster dirs in paths: %r' % paths)
239 238 for path in paths:
240 239 files = os.listdir(path)
241 240 for f in files:
242 241 full_path = os.path.join(path, f)
243 242 if os.path.isdir(full_path) and f.startswith('cluster_'):
244 243 profile = full_path.split('_')[-1]
245 244 start_cmd = 'ipcluster start -p %s -n 4' % profile
246 245 print start_cmd + " ==> " + full_path
247 246
248 247 def pre_construct(self):
249 248 # IPClusterApp.pre_construct() is where we cd to the working directory.
250 249 super(IPClusterApp, self).pre_construct()
251 250 config = self.master_config
252 251 try:
253 252 daemon = config.Global.daemonize
254 253 if daemon:
255 254 config.Global.log_to_file = True
256 255 except AttributeError:
257 256 pass
258 257
259 258 def construct(self):
260 259 config = self.master_config
261 260 subcmd = config.Global.subcommand
262 261 reset = config.Global.reset_config
263 262 if subcmd == 'list':
264 263 return
265 264 if subcmd == 'create':
266 265 self.log.info('Copying default config files to cluster directory '
267 266 '[overwrite=%r]' % (reset,))
268 267 self.cluster_dir_obj.copy_all_config_files(overwrite=reset)
269 268 if subcmd =='start':
270 269 self.cluster_dir_obj.copy_all_config_files(overwrite=False)
271 270 self.start_logging()
272 271 reactor.callWhenRunning(self.start_launchers)
273 272
274 273 def start_launchers(self):
275 274 config = self.master_config
276 275
277 276 # Create the launchers. In both bases, we set the work_dir of
278 277 # the launcher to the cluster_dir. This is where the launcher's
279 278 # subprocesses will be launched. It is not where the controller
280 279 # and engine will be launched.
281 280 el_class = import_item(config.Global.engine_launcher)
282 281 self.engine_launcher = el_class(
283 282 work_dir=self.cluster_dir, config=config
284 283 )
285 284 cl_class = import_item(config.Global.controller_launcher)
286 285 self.controller_launcher = cl_class(
287 286 work_dir=self.cluster_dir, config=config
288 287 )
289 288
290 289 # Setup signals
291 290 signal.signal(signal.SIGINT, self.sigint_handler)
292 291
293 292 # Setup the observing of stopping. If the controller dies, shut
294 293 # everything down as that will be completely fatal for the engines.
295 294 d1 = self.controller_launcher.observe_stop()
296 295 d1.addCallback(self.stop_launchers)
297 296 # But, we don't monitor the stopping of engines. An engine dying
298 297 # is just fine and in principle a user could start a new engine.
299 298 # Also, if we did monitor engine stopping, it is difficult to
300 299 # know what to do when only some engines die. Currently, the
301 300 # observing of engine stopping is inconsistent. Some launchers
302 301 # might trigger on a single engine stopping, other wait until
303 302 # all stop. TODO: think more about how to handle this.
304 303
305 304 # Start the controller and engines
306 305 self._stopping = False # Make sure stop_launchers is not called 2x.
307 306 d = self.start_controller()
308 307 d.addCallback(self.start_engines)
309 308 d.addCallback(self.startup_message)
310 309 # If the controller or engines fail to start, stop everything
311 310 d.addErrback(self.stop_launchers)
312 311 return d
313 312
314 313 def startup_message(self, r=None):
315 314 log.msg("IPython cluster: started")
316 315 return r
317 316
318 317 def start_controller(self, r=None):
319 318 # log.msg("In start_controller")
320 319 config = self.master_config
321 320 d = self.controller_launcher.start(
322 321 cluster_dir=config.Global.cluster_dir
323 322 )
324 323 return d
325 324
326 325 def start_engines(self, r=None):
327 326 # log.msg("In start_engines")
328 327 config = self.master_config
329 328 d = self.engine_launcher.start(
330 329 config.Global.n,
331 330 cluster_dir=config.Global.cluster_dir
332 331 )
333 332 return d
334 333
335 334 def stop_controller(self, r=None):
336 335 # log.msg("In stop_controller")
337 336 if self.controller_launcher.running:
338 337 d = self.controller_launcher.stop()
339 338 d.addErrback(self.log_err)
340 339 return d
341 340 else:
342 341 return defer.succeed(None)
343 342
344 343 def stop_engines(self, r=None):
345 344 # log.msg("In stop_engines")
346 345 if self.engine_launcher.running:
347 346 d = self.engine_launcher.stop()
348 347 d.addErrback(self.log_err)
349 348 return d
350 349 else:
351 350 return defer.succeed(None)
352 351
353 352 def log_err(self, f):
354 353 log.msg(f.getTraceback())
355 354 return None
356 355
357 356 def stop_launchers(self, r=None):
358 357 if not self._stopping:
359 358 self._stopping = True
360 359 if isinstance(r, failure.Failure):
361 360 log.msg('Unexpected error in ipcluster:')
362 361 log.msg(r.getTraceback())
363 362 log.msg("IPython cluster: stopping")
364 363 # These return deferreds. We are not doing anything with them
365 364 # but we are holding refs to them as a reminder that they
366 365 # do return deferreds.
367 366 d1 = self.stop_engines()
368 367 d2 = self.stop_controller()
369 368 # Wait a few seconds to let things shut down.
370 369 reactor.callLater(4.0, reactor.stop)
371 370
372 371 def sigint_handler(self, signum, frame):
373 372 self.stop_launchers()
374 373
375 374 def start_logging(self):
376 375 # Remove old log files of the controller and engine
377 376 if self.master_config.Global.clean_logs:
378 377 log_dir = self.master_config.Global.log_dir
379 378 for f in os.listdir(log_dir):
380 379 if f.startswith('ipengine' + '-'):
381 380 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
382 381 os.remove(os.path.join(log_dir, f))
383 382 if f.startswith('ipcontroller' + '-'):
384 383 if f.endswith('.log') or f.endswith('.out') or f.endswith('.err'):
385 384 os.remove(os.path.join(log_dir, f))
386 385 # This will remote old log files for ipcluster itself
387 386 super(IPClusterApp, self).start_logging()
388 387
389 388 def start_app(self):
390 389 """Start the application, depending on what subcommand is used."""
391 390 subcmd = self.master_config.Global.subcommand
392 391 if subcmd=='create' or subcmd=='list':
393 392 return
394 393 elif subcmd=='start':
395 394 self.start_app_start()
396 395 elif subcmd=='stop':
397 396 self.start_app_stop()
398 397
399 398 def start_app_start(self):
400 399 """Start the app for the start subcommand."""
401 400 config = self.master_config
402 401 # First see if the cluster is already running
403 402 try:
404 403 pid = self.get_pid_from_file()
405 404 except PIDFileError:
406 405 pass
407 406 else:
408 407 self.log.critical(
409 408 'Cluster is already running with [pid=%s]. '
410 409 'use "ipcluster stop" to stop the cluster.' % pid
411 410 )
412 411 # Here I exit with a unusual exit status that other processes
413 412 # can watch for to learn how I existed.
414 413 self.exit(ALREADY_STARTED)
415 414
416 415 # Now log and daemonize
417 416 self.log.info(
418 417 'Starting ipcluster with [daemon=%r]' % config.Global.daemonize
419 418 )
420 419 # TODO: Get daemonize working on Windows or as a Windows Server.
421 420 if config.Global.daemonize:
422 421 if os.name=='posix':
423 422 daemonize()
424 423
425 424 # Now write the new pid file AFTER our new forked pid is active.
426 425 self.write_pid_file()
427 426 reactor.addSystemEventTrigger('during','shutdown', self.remove_pid_file)
428 427 reactor.run()
429 428
430 429 def start_app_stop(self):
431 430 """Start the app for the stop subcommand."""
432 431 config = self.master_config
433 432 try:
434 433 pid = self.get_pid_from_file()
435 434 except PIDFileError:
436 435 self.log.critical(
437 436 'Problem reading pid file, cluster is probably not running.'
438 437 )
439 438 # Here I exit with a unusual exit status that other processes
440 439 # can watch for to learn how I existed.
441 440 self.exit(ALREADY_STOPPED)
442 441 else:
443 442 if os.name=='posix':
444 443 sig = config.Global.signal
445 444 self.log.info(
446 445 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
447 446 )
448 447 os.kill(pid, sig)
449 448 elif os.name=='nt':
450 449 # As of right now, we don't support daemonize on Windows, so
451 450 # stop will not do anything. Minimally, it should clean up the
452 451 # old .pid files.
453 452 self.remove_pid_file()
454 453
455 454 def launch_new_instance():
456 455 """Create and run the IPython cluster."""
457 456 app = IPClusterApp()
458 457 app.start()
459 458
460 459
461 460 if __name__ == '__main__':
462 461 launch_new_instance()
463 462
General Comments 0
You need to be logged in to leave comments. Login now