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