##// END OF EJS Templates
Tweak code with suggestions from yesterday.
Thomas Kluyver -
Show More
@@ -1,375 +1,376 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # coding: utf-8
2 # coding: utf-8
3 """A simple configuration system.
3 """A simple configuration system.
4
4
5 Authors
5 Authors
6 -------
6 -------
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2009 The IPython Development Team
12 # Copyright (C) 2008-2009 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import __builtin__
22 import __builtin__
23 import os
23 import os
24 import sys
24 import sys
25
25
26 from IPython.external import argparse
26 from IPython.external import argparse
27 from IPython.utils.path import filefind
27 from IPython.utils.path import filefind
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Exceptions
30 # Exceptions
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33
33
34 class ConfigError(Exception):
34 class ConfigError(Exception):
35 pass
35 pass
36
36
37
37
38 class ConfigLoaderError(ConfigError):
38 class ConfigLoaderError(ConfigError):
39 pass
39 pass
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Argparse fix
42 # Argparse fix
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 # Unfortunately argparse by default prints help messages to stderr instead of
45 # Unfortunately argparse by default prints help messages to stderr instead of
46 # stdout. This makes it annoying to capture long help screens at the command
46 # stdout. This makes it annoying to capture long help screens at the command
47 # line, since one must know how to pipe stderr, which many users don't know how
47 # line, since one must know how to pipe stderr, which many users don't know how
48 # to do. So we override the print_help method with one that defaults to
48 # to do. So we override the print_help method with one that defaults to
49 # stdout and use our class instead.
49 # stdout and use our class instead.
50
50
51 class ArgumentParser(argparse.ArgumentParser):
51 class ArgumentParser(argparse.ArgumentParser):
52 """Simple argparse subclass that prints help to stdout by default."""
52 """Simple argparse subclass that prints help to stdout by default."""
53
53
54 def print_help(self, file=None):
54 def print_help(self, file=None):
55 if file is None:
55 if file is None:
56 file = sys.stdout
56 file = sys.stdout
57 return super(ArgumentParser, self).print_help(file)
57 return super(ArgumentParser, self).print_help(file)
58
58
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Config class for holding config information
62 # Config class for holding config information
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 class Config(dict):
66 class Config(dict):
67 """An attribute based dict that can do smart merges."""
67 """An attribute based dict that can do smart merges."""
68
68
69 def __init__(self, *args, **kwds):
69 def __init__(self, *args, **kwds):
70 dict.__init__(self, *args, **kwds)
70 dict.__init__(self, *args, **kwds)
71 # This sets self.__dict__ = self, but it has to be done this way
71 # This sets self.__dict__ = self, but it has to be done this way
72 # because we are also overriding __setattr__.
72 # because we are also overriding __setattr__.
73 dict.__setattr__(self, '__dict__', self)
73 dict.__setattr__(self, '__dict__', self)
74
74
75 def _merge(self, other):
75 def _merge(self, other):
76 to_update = {}
76 to_update = {}
77 for k, v in other.iteritems():
77 for k, v in other.iteritems():
78 if not self.has_key(k):
78 if not self.has_key(k):
79 to_update[k] = v
79 to_update[k] = v
80 else: # I have this key
80 else: # I have this key
81 if isinstance(v, Config):
81 if isinstance(v, Config):
82 # Recursively merge common sub Configs
82 # Recursively merge common sub Configs
83 self[k]._merge(v)
83 self[k]._merge(v)
84 else:
84 else:
85 # Plain updates for non-Configs
85 # Plain updates for non-Configs
86 to_update[k] = v
86 to_update[k] = v
87
87
88 self.update(to_update)
88 self.update(to_update)
89
89
90 def _is_section_key(self, key):
90 def _is_section_key(self, key):
91 if key[0].upper()==key[0] and not key.startswith('_'):
91 if key[0].upper()==key[0] and not key.startswith('_'):
92 return True
92 return True
93 else:
93 else:
94 return False
94 return False
95
95
96 def __contains__(self, key):
96 def __contains__(self, key):
97 if self._is_section_key(key):
97 if self._is_section_key(key):
98 return True
98 return True
99 else:
99 else:
100 return super(Config, self).__contains__(key)
100 return super(Config, self).__contains__(key)
101 # .has_key is deprecated for dictionaries.
101 # .has_key is deprecated for dictionaries.
102 has_key = __contains__
102 has_key = __contains__
103
103
104 def _has_section(self, key):
104 def _has_section(self, key):
105 if self._is_section_key(key):
105 if self._is_section_key(key):
106 if super(Config, self).__contains__(key):
106 if super(Config, self).__contains__(key):
107 return True
107 return True
108 return False
108 return False
109
109
110 def copy(self):
110 def copy(self):
111 return type(self)(dict.copy(self))
111 return type(self)(dict.copy(self))
112
112
113 def __copy__(self):
113 def __copy__(self):
114 return self.copy()
114 return self.copy()
115
115
116 def __deepcopy__(self, memo):
116 def __deepcopy__(self, memo):
117 import copy
117 import copy
118 return type(self)(copy.deepcopy(self.items()))
118 return type(self)(copy.deepcopy(self.items()))
119
119
120 def __getitem__(self, key):
120 def __getitem__(self, key):
121 # Because we use this for an exec namespace, we need to delegate
121 # Because we use this for an exec namespace, we need to delegate
122 # the lookup of names in __builtin__ to itself. This means
122 # the lookup of names in __builtin__ to itself. This means
123 # that you can't have section or attribute names that are
123 # that you can't have section or attribute names that are
124 # builtins.
124 # builtins.
125 try:
125 try:
126 return getattr(__builtin__, key)
126 return getattr(__builtin__, key)
127 except AttributeError:
127 except AttributeError:
128 pass
128 pass
129 if self._is_section_key(key):
129 if self._is_section_key(key):
130 try:
130 try:
131 return dict.__getitem__(self, key)
131 return dict.__getitem__(self, key)
132 except KeyError:
132 except KeyError:
133 c = Config()
133 c = Config()
134 dict.__setitem__(self, key, c)
134 dict.__setitem__(self, key, c)
135 return c
135 return c
136 else:
136 else:
137 return dict.__getitem__(self, key)
137 return dict.__getitem__(self, key)
138
138
139 def __setitem__(self, key, value):
139 def __setitem__(self, key, value):
140 # Don't allow names in __builtin__ to be modified.
140 # Don't allow names in __builtin__ to be modified.
141 if hasattr(__builtin__, key):
141 if hasattr(__builtin__, key):
142 raise ConfigError('Config variable names cannot have the same name '
142 raise ConfigError('Config variable names cannot have the same name '
143 'as a Python builtin: %s' % key)
143 'as a Python builtin: %s' % key)
144 if self._is_section_key(key):
144 if self._is_section_key(key):
145 if not isinstance(value, Config):
145 if not isinstance(value, Config):
146 raise ValueError('values whose keys begin with an uppercase '
146 raise ValueError('values whose keys begin with an uppercase '
147 'char must be Config instances: %r, %r' % (key, value))
147 'char must be Config instances: %r, %r' % (key, value))
148 else:
148 else:
149 dict.__setitem__(self, key, value)
149 dict.__setitem__(self, key, value)
150
150
151 def __getattr__(self, key):
151 def __getattr__(self, key):
152 try:
152 try:
153 return self.__getitem__(key)
153 return self.__getitem__(key)
154 except KeyError, e:
154 except KeyError, e:
155 raise AttributeError(e)
155 raise AttributeError(e)
156
156
157 def __setattr__(self, key, value):
157 def __setattr__(self, key, value):
158 try:
158 try:
159 self.__setitem__(key, value)
159 self.__setitem__(key, value)
160 except KeyError, e:
160 except KeyError, e:
161 raise AttributeError(e)
161 raise AttributeError(e)
162
162
163 def __delattr__(self, key):
163 def __delattr__(self, key):
164 try:
164 try:
165 dict.__delitem__(self, key)
165 dict.__delitem__(self, key)
166 except KeyError, e:
166 except KeyError, e:
167 raise AttributeError(e)
167 raise AttributeError(e)
168
168
169
169
170 #-----------------------------------------------------------------------------
170 #-----------------------------------------------------------------------------
171 # Config loading classes
171 # Config loading classes
172 #-----------------------------------------------------------------------------
172 #-----------------------------------------------------------------------------
173
173
174
174
175 class ConfigLoader(object):
175 class ConfigLoader(object):
176 """A object for loading configurations from just about anywhere.
176 """A object for loading configurations from just about anywhere.
177
177
178 The resulting configuration is packaged as a :class:`Struct`.
178 The resulting configuration is packaged as a :class:`Struct`.
179
179
180 Notes
180 Notes
181 -----
181 -----
182 A :class:`ConfigLoader` does one thing: load a config from a source
182 A :class:`ConfigLoader` does one thing: load a config from a source
183 (file, command line arguments) and returns the data as a :class:`Struct`.
183 (file, command line arguments) and returns the data as a :class:`Struct`.
184 There are lots of things that :class:`ConfigLoader` does not do. It does
184 There are lots of things that :class:`ConfigLoader` does not do. It does
185 not implement complex logic for finding config files. It does not handle
185 not implement complex logic for finding config files. It does not handle
186 default values or merge multiple configs. These things need to be
186 default values or merge multiple configs. These things need to be
187 handled elsewhere.
187 handled elsewhere.
188 """
188 """
189
189
190 def __init__(self):
190 def __init__(self):
191 """A base class for config loaders.
191 """A base class for config loaders.
192
192
193 Examples
193 Examples
194 --------
194 --------
195
195
196 >>> cl = ConfigLoader()
196 >>> cl = ConfigLoader()
197 >>> config = cl.load_config()
197 >>> config = cl.load_config()
198 >>> config
198 >>> config
199 {}
199 {}
200 """
200 """
201 self.clear()
201 self.clear()
202
202
203 def clear(self):
203 def clear(self):
204 self.config = Config()
204 self.config = Config()
205
205
206 def load_config(self):
206 def load_config(self):
207 """Load a config from somewhere, return a :class:`Config` instance.
207 """Load a config from somewhere, return a :class:`Config` instance.
208
208
209 Usually, this will cause self.config to be set and then returned.
209 Usually, this will cause self.config to be set and then returned.
210 However, in most cases, :meth:`ConfigLoader.clear` should be called
210 However, in most cases, :meth:`ConfigLoader.clear` should be called
211 to erase any previous state.
211 to erase any previous state.
212 """
212 """
213 self.clear()
213 self.clear()
214 return self.config
214 return self.config
215
215
216
216
217 class FileConfigLoader(ConfigLoader):
217 class FileConfigLoader(ConfigLoader):
218 """A base class for file based configurations.
218 """A base class for file based configurations.
219
219
220 As we add more file based config loaders, the common logic should go
220 As we add more file based config loaders, the common logic should go
221 here.
221 here.
222 """
222 """
223 pass
223 pass
224
224
225
225
226 class PyFileConfigLoader(FileConfigLoader):
226 class PyFileConfigLoader(FileConfigLoader):
227 """A config loader for pure python files.
227 """A config loader for pure python files.
228
228
229 This calls execfile on a plain python file and looks for attributes
229 This calls execfile on a plain python file and looks for attributes
230 that are all caps. These attribute are added to the config Struct.
230 that are all caps. These attribute are added to the config Struct.
231 """
231 """
232
232
233 def __init__(self, filename, path=None):
233 def __init__(self, filename, path=None):
234 """Build a config loader for a filename and path.
234 """Build a config loader for a filename and path.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 filename : str
238 filename : str
239 The file name of the config file.
239 The file name of the config file.
240 path : str, list, tuple
240 path : str, list, tuple
241 The path to search for the config file on, or a sequence of
241 The path to search for the config file on, or a sequence of
242 paths to try in order.
242 paths to try in order.
243 """
243 """
244 super(PyFileConfigLoader, self).__init__()
244 super(PyFileConfigLoader, self).__init__()
245 self.filename = filename
245 self.filename = filename
246 self.path = path
246 self.path = path
247 self.full_filename = ''
247 self.full_filename = ''
248 self.data = None
248 self.data = None
249
249
250 def load_config(self):
250 def load_config(self):
251 """Load the config from a file and return it as a Struct."""
251 """Load the config from a file and return it as a Struct."""
252 self.clear()
252 self.clear()
253 self._find_file()
253 self._find_file()
254 self._read_file_as_dict()
254 self._read_file_as_dict()
255 self._convert_to_config()
255 self._convert_to_config()
256 return self.config
256 return self.config
257
257
258 def _find_file(self):
258 def _find_file(self):
259 """Try to find the file by searching the paths."""
259 """Try to find the file by searching the paths."""
260 self.full_filename = filefind(self.filename, self.path)
260 self.full_filename = filefind(self.filename, self.path)
261
261
262 def _read_file_as_dict(self):
262 def _read_file_as_dict(self):
263 """Load the config file into self.config, with recursive loading."""
263 """Load the config file into self.config, with recursive loading."""
264 # This closure is made available in the namespace that is used
264 # This closure is made available in the namespace that is used
265 # to exec the config file. This allows users to call
265 # to exec the config file. This allows users to call
266 # load_subconfig('myconfig.py') to load config files recursively.
266 # load_subconfig('myconfig.py') to load config files recursively.
267 # It needs to be a closure because it has references to self.path
267 # It needs to be a closure because it has references to self.path
268 # and self.config. The sub-config is loaded with the same path
268 # and self.config. The sub-config is loaded with the same path
269 # as the parent, but it uses an empty config which is then merged
269 # as the parent, but it uses an empty config which is then merged
270 # with the parents.
270 # with the parents.
271 def load_subconfig(fname):
271 def load_subconfig(fname):
272 loader = PyFileConfigLoader(fname, self.path)
272 loader = PyFileConfigLoader(fname, self.path)
273 try:
273 try:
274 sub_config = loader.load_config()
274 sub_config = loader.load_config()
275 except IOError:
275 except IOError:
276 # Pass silently if the sub config is not there. This happens
276 # Pass silently if the sub config is not there. This happens
277 # when a user us using a profile, but not the default config.
277 # when a user us using a profile, but not the default config.
278 pass
278 pass
279 else:
279 else:
280 self.config._merge(sub_config)
280 self.config._merge(sub_config)
281
281
282 # Again, this needs to be a closure and should be used in config
282 # Again, this needs to be a closure and should be used in config
283 # files to get the config being loaded.
283 # files to get the config being loaded.
284 def get_config():
284 def get_config():
285 return self.config
285 return self.config
286
286
287 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
287 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
288 conf_filename = self.full_filename.encode(sys.getfilesystemencoding())
288 fs_encoding = sys.getfilesystemencoding() or 'ascii'
289 conf_filename = self.full_filename.encode(fs_encoding)
289 execfile(conf_filename, namespace)
290 execfile(conf_filename, namespace)
290
291
291 def _convert_to_config(self):
292 def _convert_to_config(self):
292 if self.data is None:
293 if self.data is None:
293 ConfigLoaderError('self.data does not exist')
294 ConfigLoaderError('self.data does not exist')
294
295
295
296
296 class CommandLineConfigLoader(ConfigLoader):
297 class CommandLineConfigLoader(ConfigLoader):
297 """A config loader for command line arguments.
298 """A config loader for command line arguments.
298
299
299 As we add more command line based loaders, the common logic should go
300 As we add more command line based loaders, the common logic should go
300 here.
301 here.
301 """
302 """
302
303
303
304
304 class ArgParseConfigLoader(CommandLineConfigLoader):
305 class ArgParseConfigLoader(CommandLineConfigLoader):
305
306
306 def __init__(self, argv=None, *parser_args, **parser_kw):
307 def __init__(self, argv=None, *parser_args, **parser_kw):
307 """Create a config loader for use with argparse.
308 """Create a config loader for use with argparse.
308
309
309 Parameters
310 Parameters
310 ----------
311 ----------
311
312
312 argv : optional, list
313 argv : optional, list
313 If given, used to read command-line arguments from, otherwise
314 If given, used to read command-line arguments from, otherwise
314 sys.argv[1:] is used.
315 sys.argv[1:] is used.
315
316
316 parser_args : tuple
317 parser_args : tuple
317 A tuple of positional arguments that will be passed to the
318 A tuple of positional arguments that will be passed to the
318 constructor of :class:`argparse.ArgumentParser`.
319 constructor of :class:`argparse.ArgumentParser`.
319
320
320 parser_kw : dict
321 parser_kw : dict
321 A tuple of keyword arguments that will be passed to the
322 A tuple of keyword arguments that will be passed to the
322 constructor of :class:`argparse.ArgumentParser`.
323 constructor of :class:`argparse.ArgumentParser`.
323 """
324 """
324 super(CommandLineConfigLoader, self).__init__()
325 super(CommandLineConfigLoader, self).__init__()
325 if argv == None:
326 if argv == None:
326 argv = sys.argv[1:]
327 argv = sys.argv[1:]
327 self.argv = argv
328 self.argv = argv
328 self.parser_args = parser_args
329 self.parser_args = parser_args
329 self.version = parser_kw.pop("version", None)
330 self.version = parser_kw.pop("version", None)
330 kwargs = dict(argument_default=argparse.SUPPRESS)
331 kwargs = dict(argument_default=argparse.SUPPRESS)
331 kwargs.update(parser_kw)
332 kwargs.update(parser_kw)
332 self.parser_kw = kwargs
333 self.parser_kw = kwargs
333
334
334 def load_config(self, args=None):
335 def load_config(self, args=None):
335 """Parse command line arguments and return as a Struct.
336 """Parse command line arguments and return as a Struct.
336
337
337 Parameters
338 Parameters
338 ----------
339 ----------
339
340
340 args : optional, list
341 args : optional, list
341 If given, a list with the structure of sys.argv[1:] to parse
342 If given, a list with the structure of sys.argv[1:] to parse
342 arguments from. If not given, the instance's self.argv attribute
343 arguments from. If not given, the instance's self.argv attribute
343 (given at construction time) is used."""
344 (given at construction time) is used."""
344 self.clear()
345 self.clear()
345 if args is None:
346 if args is None:
346 args = self.argv
347 args = self.argv
347 self._create_parser()
348 self._create_parser()
348 self._parse_args(args)
349 self._parse_args(args)
349 self._convert_to_config()
350 self._convert_to_config()
350 return self.config
351 return self.config
351
352
352 def get_extra_args(self):
353 def get_extra_args(self):
353 if hasattr(self, 'extra_args'):
354 if hasattr(self, 'extra_args'):
354 return self.extra_args
355 return self.extra_args
355 else:
356 else:
356 return []
357 return []
357
358
358 def _create_parser(self):
359 def _create_parser(self):
359 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
360 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
360 self._add_arguments()
361 self._add_arguments()
361
362
362 def _add_arguments(self):
363 def _add_arguments(self):
363 raise NotImplementedError("subclasses must implement _add_arguments")
364 raise NotImplementedError("subclasses must implement _add_arguments")
364
365
365 def _parse_args(self, args):
366 def _parse_args(self, args):
366 """self.parser->self.parsed_data"""
367 """self.parser->self.parsed_data"""
367 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
368 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
368
369
369 def _convert_to_config(self):
370 def _convert_to_config(self):
370 """self.parsed_data->self.config"""
371 """self.parsed_data->self.config"""
371 for k, v in vars(self.parsed_data).iteritems():
372 for k, v in vars(self.parsed_data).iteritems():
372 exec_str = 'self.config.' + k + '= v'
373 exec_str = 'self.config.' + k + '= v'
373 exec exec_str in locals(), globals()
374 exec exec_str in locals(), globals()
374
375
375
376
@@ -1,461 +1,465 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15
15
16 Notes
16 Notes
17 -----
17 -----
18 """
18 """
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Copyright (C) 2008-2009 The IPython Development Team
21 # Copyright (C) 2008-2009 The IPython Development Team
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Imports
28 # Imports
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 import logging
31 import logging
32 import os
32 import os
33 import sys
33 import sys
34
34
35 from IPython.core import release, crashhandler
35 from IPython.core import release, crashhandler
36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 from IPython.config.loader import (
37 from IPython.config.loader import (
38 PyFileConfigLoader,
38 PyFileConfigLoader,
39 ArgParseConfigLoader,
39 ArgParseConfigLoader,
40 Config,
40 Config,
41 )
41 )
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Classes and functions
44 # Classes and functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 class ApplicationError(Exception):
47 class ApplicationError(Exception):
48 pass
48 pass
49
49
50
50
51 class BaseAppConfigLoader(ArgParseConfigLoader):
51 class BaseAppConfigLoader(ArgParseConfigLoader):
52 """Default command line options for IPython based applications."""
52 """Default command line options for IPython based applications."""
53
53
54 def _add_ipython_dir(self, parser):
54 def _add_ipython_dir(self, parser):
55 """Add the --ipython-dir option to the parser."""
55 """Add the --ipython-dir option to the parser."""
56 paa = parser.add_argument
56 paa = parser.add_argument
57 paa('--ipython-dir',
57 paa('--ipython-dir',
58 dest='Global.ipython_dir',type=unicode,
58 dest='Global.ipython_dir',type=unicode,
59 help=
59 help=
60 """Set to override default location of the IPython directory
60 """Set to override default location of the IPython directory
61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
62 specified through the environment variable IPYTHON_DIR.""",
62 specified through the environment variable IPYTHON_DIR.""",
63 metavar='Global.ipython_dir')
63 metavar='Global.ipython_dir')
64
64
65 def _add_log_level(self, parser):
65 def _add_log_level(self, parser):
66 """Add the --log-level option to the parser."""
66 """Add the --log-level option to the parser."""
67 paa = parser.add_argument
67 paa = parser.add_argument
68 paa('--log-level',
68 paa('--log-level',
69 dest="Global.log_level",type=int,
69 dest="Global.log_level",type=int,
70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
71 metavar='Global.log_level')
71 metavar='Global.log_level')
72
72
73 def _add_version(self, parser):
73 def _add_version(self, parser):
74 """Add the --version option to the parser."""
74 """Add the --version option to the parser."""
75 parser.add_argument('--version', action="version",
75 parser.add_argument('--version', action="version",
76 version=self.version)
76 version=self.version)
77
77
78 def _add_arguments(self):
78 def _add_arguments(self):
79 self._add_ipython_dir(self.parser)
79 self._add_ipython_dir(self.parser)
80 self._add_log_level(self.parser)
80 self._add_log_level(self.parser)
81 self._add_version(self.parser)
81 self._add_version(self.parser)
82
82
83
83
84 class Application(object):
84 class Application(object):
85 """Load a config, construct configurables and set them running.
85 """Load a config, construct configurables and set them running.
86
86
87 The configuration of an application can be done via three different Config
87 The configuration of an application can be done via three different Config
88 objects, which are loaded and ultimately merged into a single one used
88 objects, which are loaded and ultimately merged into a single one used
89 from that point on by the app. These are:
89 from that point on by the app. These are:
90
90
91 1. default_config: internal defaults, implemented in code.
91 1. default_config: internal defaults, implemented in code.
92 2. file_config: read from the filesystem.
92 2. file_config: read from the filesystem.
93 3. command_line_config: read from the system's command line flags.
93 3. command_line_config: read from the system's command line flags.
94
94
95 During initialization, 3 is actually read before 2, since at the
95 During initialization, 3 is actually read before 2, since at the
96 command-line one may override the location of the file to be read. But the
96 command-line one may override the location of the file to be read. But the
97 above is the order in which the merge is made.
97 above is the order in which the merge is made.
98 """
98 """
99
99
100 name = u'ipython'
100 name = u'ipython'
101 description = 'IPython: an enhanced interactive Python shell.'
101 description = 'IPython: an enhanced interactive Python shell.'
102 #: Usage message printed by argparse. If None, auto-generate
102 #: Usage message printed by argparse. If None, auto-generate
103 usage = None
103 usage = None
104 #: The command line config loader. Subclass of ArgParseConfigLoader.
104 #: The command line config loader. Subclass of ArgParseConfigLoader.
105 command_line_loader = BaseAppConfigLoader
105 command_line_loader = BaseAppConfigLoader
106 #: The name of the config file to load, determined at runtime
106 #: The name of the config file to load, determined at runtime
107 config_file_name = None
107 config_file_name = None
108 #: The name of the default config file. Track separately from the actual
108 #: The name of the default config file. Track separately from the actual
109 #: name because some logic happens only if we aren't using the default.
109 #: name because some logic happens only if we aren't using the default.
110 default_config_file_name = u'ipython_config.py'
110 default_config_file_name = u'ipython_config.py'
111 default_log_level = logging.WARN
111 default_log_level = logging.WARN
112 #: Set by --profile option
112 #: Set by --profile option
113 profile_name = None
113 profile_name = None
114 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
114 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
115 ipython_dir = None
115 ipython_dir = None
116 #: Internal defaults, implemented in code.
116 #: Internal defaults, implemented in code.
117 default_config = None
117 default_config = None
118 #: Read from the filesystem.
118 #: Read from the filesystem.
119 file_config = None
119 file_config = None
120 #: Read from the system's command line flags.
120 #: Read from the system's command line flags.
121 command_line_config = None
121 command_line_config = None
122 #: The final config that will be passed to the main object.
122 #: The final config that will be passed to the main object.
123 master_config = None
123 master_config = None
124 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
124 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
125 argv = None
125 argv = None
126 #: extra arguments computed by the command-line loader
126 #: extra arguments computed by the command-line loader
127 extra_args = None
127 extra_args = None
128 #: The class to use as the crash handler.
128 #: The class to use as the crash handler.
129 crash_handler_class = crashhandler.CrashHandler
129 crash_handler_class = crashhandler.CrashHandler
130
130
131 # Private attributes
131 # Private attributes
132 _exiting = False
132 _exiting = False
133 _initialized = False
133 _initialized = False
134
134
135 def __init__(self, argv=None):
135 def __init__(self, argv=None):
136 self.argv = sys.argv[1:] if argv is None else argv
136 self.argv = sys.argv[1:] if argv is None else argv
137 self.init_logger()
137 self.init_logger()
138
138
139 def init_logger(self):
139 def init_logger(self):
140 self.log = logging.getLogger(self.__class__.__name__)
140 self.log = logging.getLogger(self.__class__.__name__)
141 # This is used as the default until the command line arguments are read.
141 # This is used as the default until the command line arguments are read.
142 self.log.setLevel(self.default_log_level)
142 self.log.setLevel(self.default_log_level)
143 self._log_handler = logging.StreamHandler()
143 self._log_handler = logging.StreamHandler()
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 self._log_handler.setFormatter(self._log_formatter)
145 self._log_handler.setFormatter(self._log_formatter)
146 self.log.addHandler(self._log_handler)
146 self.log.addHandler(self._log_handler)
147
147
148 def _set_log_level(self, level):
148 def _set_log_level(self, level):
149 self.log.setLevel(level)
149 self.log.setLevel(level)
150
150
151 def _get_log_level(self):
151 def _get_log_level(self):
152 return self.log.level
152 return self.log.level
153
153
154 log_level = property(_get_log_level, _set_log_level)
154 log_level = property(_get_log_level, _set_log_level)
155
155
156 def initialize(self):
156 def initialize(self):
157 """Initialize the application.
157 """Initialize the application.
158
158
159 Loads all configuration information and sets all application state, but
159 Loads all configuration information and sets all application state, but
160 does not start any relevant processing (typically some kind of event
160 does not start any relevant processing (typically some kind of event
161 loop).
161 loop).
162
162
163 Once this method has been called, the application is flagged as
163 Once this method has been called, the application is flagged as
164 initialized and the method becomes a no-op."""
164 initialized and the method becomes a no-op."""
165
165
166 if self._initialized:
166 if self._initialized:
167 return
167 return
168
168
169 # The first part is protected with an 'attempt' wrapper, that will log
169 # The first part is protected with an 'attempt' wrapper, that will log
170 # failures with the basic system traceback machinery. Once our crash
170 # failures with the basic system traceback machinery. Once our crash
171 # handler is in place, we can let any subsequent exception propagate,
171 # handler is in place, we can let any subsequent exception propagate,
172 # as our handler will log it with much better detail than the default.
172 # as our handler will log it with much better detail than the default.
173 self.attempt(self.create_crash_handler)
173 self.attempt(self.create_crash_handler)
174
174
175 # Configuration phase
175 # Configuration phase
176 # Default config (internally hardwired in application code)
176 # Default config (internally hardwired in application code)
177 self.create_default_config()
177 self.create_default_config()
178 self.log_default_config()
178 self.log_default_config()
179 self.set_default_config_log_level()
179 self.set_default_config_log_level()
180
180
181 # Command-line config
181 # Command-line config
182 self.pre_load_command_line_config()
182 self.pre_load_command_line_config()
183 self.load_command_line_config()
183 self.load_command_line_config()
184 self.set_command_line_config_log_level()
184 self.set_command_line_config_log_level()
185 self.post_load_command_line_config()
185 self.post_load_command_line_config()
186 self.log_command_line_config()
186 self.log_command_line_config()
187
187
188 # Find resources needed for filesystem access, using information from
188 # Find resources needed for filesystem access, using information from
189 # the above two
189 # the above two
190 self.find_ipython_dir()
190 self.find_ipython_dir()
191 self.find_resources()
191 self.find_resources()
192 self.find_config_file_name()
192 self.find_config_file_name()
193 self.find_config_file_paths()
193 self.find_config_file_paths()
194
194
195 # File-based config
195 # File-based config
196 self.pre_load_file_config()
196 self.pre_load_file_config()
197 self.load_file_config()
197 self.load_file_config()
198 self.set_file_config_log_level()
198 self.set_file_config_log_level()
199 self.post_load_file_config()
199 self.post_load_file_config()
200 self.log_file_config()
200 self.log_file_config()
201
201
202 # Merge all config objects into a single one the app can then use
202 # Merge all config objects into a single one the app can then use
203 self.merge_configs()
203 self.merge_configs()
204 self.log_master_config()
204 self.log_master_config()
205
205
206 # Construction phase
206 # Construction phase
207 self.pre_construct()
207 self.pre_construct()
208 self.construct()
208 self.construct()
209 self.post_construct()
209 self.post_construct()
210
210
211 # Done, flag as such and
211 # Done, flag as such and
212 self._initialized = True
212 self._initialized = True
213
213
214 def start(self):
214 def start(self):
215 """Start the application."""
215 """Start the application."""
216 self.initialize()
216 self.initialize()
217 self.start_app()
217 self.start_app()
218
218
219 #-------------------------------------------------------------------------
219 #-------------------------------------------------------------------------
220 # Various stages of Application creation
220 # Various stages of Application creation
221 #-------------------------------------------------------------------------
221 #-------------------------------------------------------------------------
222
222
223 def create_crash_handler(self):
223 def create_crash_handler(self):
224 """Create a crash handler, typically setting sys.excepthook to it."""
224 """Create a crash handler, typically setting sys.excepthook to it."""
225 self.crash_handler = self.crash_handler_class(self)
225 self.crash_handler = self.crash_handler_class(self)
226 sys.excepthook = self.crash_handler
226 sys.excepthook = self.crash_handler
227
227
228 def create_default_config(self):
228 def create_default_config(self):
229 """Create defaults that can't be set elsewhere.
229 """Create defaults that can't be set elsewhere.
230
230
231 For the most part, we try to set default in the class attributes
231 For the most part, we try to set default in the class attributes
232 of Configurables. But, defaults the top-level Application (which is
232 of Configurables. But, defaults the top-level Application (which is
233 not a HasTraits or Configurables) are not set in this way. Instead
233 not a HasTraits or Configurables) are not set in this way. Instead
234 we set them here. The Global section is for variables like this that
234 we set them here. The Global section is for variables like this that
235 don't belong to a particular configurable.
235 don't belong to a particular configurable.
236 """
236 """
237 c = Config()
237 c = Config()
238 c.Global.ipython_dir = get_ipython_dir()
238 c.Global.ipython_dir = get_ipython_dir()
239 c.Global.log_level = self.log_level
239 c.Global.log_level = self.log_level
240 self.default_config = c
240 self.default_config = c
241
241
242 def log_default_config(self):
242 def log_default_config(self):
243 self.log.debug('Default config loaded:')
243 self.log.debug('Default config loaded:')
244 self.log.debug(repr(self.default_config))
244 self.log.debug(repr(self.default_config))
245
245
246 def set_default_config_log_level(self):
246 def set_default_config_log_level(self):
247 try:
247 try:
248 self.log_level = self.default_config.Global.log_level
248 self.log_level = self.default_config.Global.log_level
249 except AttributeError:
249 except AttributeError:
250 # Fallback to the default_log_level class attribute
250 # Fallback to the default_log_level class attribute
251 pass
251 pass
252
252
253 def create_command_line_config(self):
253 def create_command_line_config(self):
254 """Create and return a command line config loader."""
254 """Create and return a command line config loader."""
255 return self.command_line_loader(
255 return self.command_line_loader(
256 self.argv,
256 self.argv,
257 description=self.description,
257 description=self.description,
258 version=release.version,
258 version=release.version,
259 usage=self.usage
259 usage=self.usage
260 )
260 )
261
261
262 def pre_load_command_line_config(self):
262 def pre_load_command_line_config(self):
263 """Do actions just before loading the command line config."""
263 """Do actions just before loading the command line config."""
264 pass
264 pass
265
265
266 def load_command_line_config(self):
266 def load_command_line_config(self):
267 """Load the command line config."""
267 """Load the command line config."""
268 loader = self.create_command_line_config()
268 loader = self.create_command_line_config()
269 self.command_line_config = loader.load_config()
269 self.command_line_config = loader.load_config()
270 self.extra_args = loader.get_extra_args()
270 self.extra_args = loader.get_extra_args()
271
271
272 def set_command_line_config_log_level(self):
272 def set_command_line_config_log_level(self):
273 try:
273 try:
274 self.log_level = self.command_line_config.Global.log_level
274 self.log_level = self.command_line_config.Global.log_level
275 except AttributeError:
275 except AttributeError:
276 pass
276 pass
277
277
278 def post_load_command_line_config(self):
278 def post_load_command_line_config(self):
279 """Do actions just after loading the command line config."""
279 """Do actions just after loading the command line config."""
280 pass
280 pass
281
281
282 def log_command_line_config(self):
282 def log_command_line_config(self):
283 self.log.debug("Command line config loaded:")
283 self.log.debug("Command line config loaded:")
284 self.log.debug(repr(self.command_line_config))
284 self.log.debug(repr(self.command_line_config))
285
285
286 def find_ipython_dir(self):
286 def find_ipython_dir(self):
287 """Set the IPython directory.
287 """Set the IPython directory.
288
288
289 This sets ``self.ipython_dir``, but the actual value that is passed to
289 This sets ``self.ipython_dir``, but the actual value that is passed to
290 the application is kept in either ``self.default_config`` or
290 the application is kept in either ``self.default_config`` or
291 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
291 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
292 ``sys.path`` so config files there can be referenced by other config
292 ``sys.path`` so config files there can be referenced by other config
293 files.
293 files.
294 """
294 """
295
295
296 try:
296 try:
297 self.ipython_dir = self.command_line_config.Global.ipython_dir
297 self.ipython_dir = self.command_line_config.Global.ipython_dir
298 except AttributeError:
298 except AttributeError:
299 self.ipython_dir = self.default_config.Global.ipython_dir
299 self.ipython_dir = self.default_config.Global.ipython_dir
300 sys.path.append(os.path.abspath(self.ipython_dir))
300 sys.path.append(os.path.abspath(self.ipython_dir))
301 if not os.path.isdir(self.ipython_dir):
301 if not os.path.isdir(self.ipython_dir):
302 os.makedirs(self.ipython_dir, mode=0777)
302 os.makedirs(self.ipython_dir, mode=0777)
303 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
303 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
304
304
305 def find_resources(self):
305 def find_resources(self):
306 """Find other resources that need to be in place.
306 """Find other resources that need to be in place.
307
307
308 Things like cluster directories need to be in place to find the
308 Things like cluster directories need to be in place to find the
309 config file. These happen right after the IPython directory has
309 config file. These happen right after the IPython directory has
310 been set.
310 been set.
311 """
311 """
312 pass
312 pass
313
313
314 def find_config_file_name(self):
314 def find_config_file_name(self):
315 """Find the config file name for this application.
315 """Find the config file name for this application.
316
316
317 This must set ``self.config_file_name`` to the filename of the
317 This must set ``self.config_file_name`` to the filename of the
318 config file to use (just the filename). The search paths for the
318 config file to use (just the filename). The search paths for the
319 config file are set in :meth:`find_config_file_paths` and then passed
319 config file are set in :meth:`find_config_file_paths` and then passed
320 to the config file loader where they are resolved to an absolute path.
320 to the config file loader where they are resolved to an absolute path.
321
321
322 If a profile has been set at the command line, this will resolve it.
322 If a profile has been set at the command line, this will resolve it.
323 """
323 """
324 try:
324 try:
325 self.config_file_name = self.command_line_config.Global.config_file
325 self.config_file_name = self.command_line_config.Global.config_file
326 except AttributeError:
326 except AttributeError:
327 pass
327 pass
328 else:
328 else:
329 return
329 return
330
330
331 try:
331 try:
332 self.profile_name = self.command_line_config.Global.profile
332 self.profile_name = self.command_line_config.Global.profile
333 except AttributeError:
333 except AttributeError:
334 # Just use the default as there is no profile
334 # Just use the default as there is no profile
335 self.config_file_name = self.default_config_file_name
335 self.config_file_name = self.default_config_file_name
336 else:
336 else:
337 # Use the default config file name and profile name if set
337 # Use the default config file name and profile name if set
338 # to determine the used config file name.
338 # to determine the used config file name.
339 name_parts = self.default_config_file_name.split('.')
339 name_parts = self.default_config_file_name.split('.')
340 name_parts.insert(1, u'_' + self.profile_name + u'.')
340 name_parts.insert(1, u'_' + self.profile_name + u'.')
341 self.config_file_name = ''.join(name_parts)
341 self.config_file_name = ''.join(name_parts)
342
342
343 def find_config_file_paths(self):
343 def find_config_file_paths(self):
344 """Set the search paths for resolving the config file.
344 """Set the search paths for resolving the config file.
345
345
346 This must set ``self.config_file_paths`` to a sequence of search
346 This must set ``self.config_file_paths`` to a sequence of search
347 paths to pass to the config file loader.
347 paths to pass to the config file loader.
348 """
348 """
349 # Include our own profiles directory last, so that users can still find
349 # Include our own profiles directory last, so that users can still find
350 # our shipped copies of builtin profiles even if they don't have them
350 # our shipped copies of builtin profiles even if they don't have them
351 # in their local ipython directory.
351 # in their local ipython directory.
352 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
352 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
353 self.config_file_paths = (os.getcwdu(), self.ipython_dir, prof_dir)
353 self.config_file_paths = (os.getcwdu(), self.ipython_dir, prof_dir)
354
354
355 def pre_load_file_config(self):
355 def pre_load_file_config(self):
356 """Do actions before the config file is loaded."""
356 """Do actions before the config file is loaded."""
357 pass
357 pass
358
358
359 def load_file_config(self, suppress_errors=True):
359 def load_file_config(self, suppress_errors=True):
360 """Load the config file.
360 """Load the config file.
361
361
362 This tries to load the config file from disk. If successful, the
362 This tries to load the config file from disk. If successful, the
363 ``CONFIG_FILE`` config variable is set to the resolved config file
363 ``CONFIG_FILE`` config variable is set to the resolved config file
364 location. If not successful, an empty config is used.
364 location. If not successful, an empty config is used.
365
366 By default, errors in loading config are handled, and a warning
367 printed on screen. For testing, the suppress_errors option is set
368 to False, so errors will make tests fail.
365 """
369 """
366 self.log.debug("Attempting to load config file: %s" %
370 self.log.debug("Attempting to load config file: %s" %
367 self.config_file_name)
371 self.config_file_name)
368 loader = PyFileConfigLoader(self.config_file_name,
372 loader = PyFileConfigLoader(self.config_file_name,
369 path=self.config_file_paths)
373 path=self.config_file_paths)
370 try:
374 try:
371 self.file_config = loader.load_config()
375 self.file_config = loader.load_config()
372 self.file_config.Global.config_file = loader.full_filename
376 self.file_config.Global.config_file = loader.full_filename
373 except IOError:
377 except IOError:
374 # Only warn if the default config file was NOT being used.
378 # Only warn if the default config file was NOT being used.
375 if not self.config_file_name==self.default_config_file_name:
379 if not self.config_file_name==self.default_config_file_name:
376 self.log.warn("Config file not found, skipping: %s" %
380 self.log.warn("Config file not found, skipping: %s" %
377 self.config_file_name, exc_info=True)
381 self.config_file_name, exc_info=True)
378 self.file_config = Config()
382 self.file_config = Config()
379 except:
383 except:
380 if not suppress_errors: # For testing purposes
384 if not suppress_errors: # For testing purposes
381 raise
385 raise
382 self.log.warn("Error loading config file: %s" %
386 self.log.warn("Error loading config file: %s" %
383 self.config_file_name, exc_info=True)
387 self.config_file_name, exc_info=True)
384 self.file_config = Config()
388 self.file_config = Config()
385
389
386 def set_file_config_log_level(self):
390 def set_file_config_log_level(self):
387 # We need to keeep self.log_level updated. But we only use the value
391 # We need to keeep self.log_level updated. But we only use the value
388 # of the file_config if a value was not specified at the command
392 # of the file_config if a value was not specified at the command
389 # line, because the command line overrides everything.
393 # line, because the command line overrides everything.
390 if not hasattr(self.command_line_config.Global, 'log_level'):
394 if not hasattr(self.command_line_config.Global, 'log_level'):
391 try:
395 try:
392 self.log_level = self.file_config.Global.log_level
396 self.log_level = self.file_config.Global.log_level
393 except AttributeError:
397 except AttributeError:
394 pass # Use existing value
398 pass # Use existing value
395
399
396 def post_load_file_config(self):
400 def post_load_file_config(self):
397 """Do actions after the config file is loaded."""
401 """Do actions after the config file is loaded."""
398 pass
402 pass
399
403
400 def log_file_config(self):
404 def log_file_config(self):
401 if hasattr(self.file_config.Global, 'config_file'):
405 if hasattr(self.file_config.Global, 'config_file'):
402 self.log.debug("Config file loaded: %s" %
406 self.log.debug("Config file loaded: %s" %
403 self.file_config.Global.config_file)
407 self.file_config.Global.config_file)
404 self.log.debug(repr(self.file_config))
408 self.log.debug(repr(self.file_config))
405
409
406 def merge_configs(self):
410 def merge_configs(self):
407 """Merge the default, command line and file config objects."""
411 """Merge the default, command line and file config objects."""
408 config = Config()
412 config = Config()
409 config._merge(self.default_config)
413 config._merge(self.default_config)
410 config._merge(self.file_config)
414 config._merge(self.file_config)
411 config._merge(self.command_line_config)
415 config._merge(self.command_line_config)
412
416
413 # XXX fperez - propose to Brian we rename master_config to simply
417 # XXX fperez - propose to Brian we rename master_config to simply
414 # config, I think this is going to be heavily used in examples and
418 # config, I think this is going to be heavily used in examples and
415 # application code and the name is shorter/easier to find/remember.
419 # application code and the name is shorter/easier to find/remember.
416 # For now, just alias it...
420 # For now, just alias it...
417 self.master_config = config
421 self.master_config = config
418 self.config = config
422 self.config = config
419
423
420 def log_master_config(self):
424 def log_master_config(self):
421 self.log.debug("Master config created:")
425 self.log.debug("Master config created:")
422 self.log.debug(repr(self.master_config))
426 self.log.debug(repr(self.master_config))
423
427
424 def pre_construct(self):
428 def pre_construct(self):
425 """Do actions after the config has been built, but before construct."""
429 """Do actions after the config has been built, but before construct."""
426 pass
430 pass
427
431
428 def construct(self):
432 def construct(self):
429 """Construct the main objects that make up this app."""
433 """Construct the main objects that make up this app."""
430 self.log.debug("Constructing main objects for application")
434 self.log.debug("Constructing main objects for application")
431
435
432 def post_construct(self):
436 def post_construct(self):
433 """Do actions after construct, but before starting the app."""
437 """Do actions after construct, but before starting the app."""
434 pass
438 pass
435
439
436 def start_app(self):
440 def start_app(self):
437 """Actually start the app."""
441 """Actually start the app."""
438 self.log.debug("Starting application")
442 self.log.debug("Starting application")
439
443
440 #-------------------------------------------------------------------------
444 #-------------------------------------------------------------------------
441 # Utility methods
445 # Utility methods
442 #-------------------------------------------------------------------------
446 #-------------------------------------------------------------------------
443
447
444 def exit(self, exit_status=0):
448 def exit(self, exit_status=0):
445 if self._exiting:
449 if self._exiting:
446 pass
450 pass
447 else:
451 else:
448 self.log.debug("Exiting application: %s" % self.name)
452 self.log.debug("Exiting application: %s" % self.name)
449 self._exiting = True
453 self._exiting = True
450 sys.exit(exit_status)
454 sys.exit(exit_status)
451
455
452 def attempt(self, func):
456 def attempt(self, func):
453 try:
457 try:
454 func()
458 func()
455 except SystemExit:
459 except SystemExit:
456 raise
460 raise
457 except:
461 except:
458 self.log.critical("Aborting application: %s" % self.name,
462 self.log.critical("Aborting application: %s" % self.name,
459 exc_info=True)
463 exc_info=True)
460 self.exit(0)
464 self.exit(0)
461
465
@@ -1,71 +1,74 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the compilerop module.
2 """Tests for the compilerop module.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2010 The IPython Development Team.
5 # Copyright (C) 2010 The IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the BSD License.
7 # Distributed under the terms of the BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Stdlib imports
17 # Stdlib imports
18 import linecache
18 import linecache
19 import sys
19
20
20 # Third-party imports
21 # Third-party imports
21 import nose.tools as nt
22 import nose.tools as nt
22
23
23 # Our own imports
24 # Our own imports
24 from IPython.core import compilerop
25 from IPython.core import compilerop
25
26
26 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
27 # Test functions
28 # Test functions
28 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
29
30
30 def test_code_name():
31 def test_code_name():
31 code = 'x=1'
32 code = 'x=1'
32 name = compilerop.code_name(code)
33 name = compilerop.code_name(code)
33 nt.assert_true(name.startswith('<ipython-input-0'))
34 nt.assert_true(name.startswith('<ipython-input-0'))
34
35
35
36
36 def test_code_name2():
37 def test_code_name2():
37 code = 'x=1'
38 code = 'x=1'
38 name = compilerop.code_name(code, 9)
39 name = compilerop.code_name(code, 9)
39 nt.assert_true(name.startswith('<ipython-input-9'))
40 nt.assert_true(name.startswith('<ipython-input-9'))
40
41
41
42
42 def test_compiler():
43 def test_compiler():
43 """Test the compiler correctly compiles and caches inputs
44 """Test the compiler correctly compiles and caches inputs
44 """
45 """
45 cp = compilerop.CachingCompiler()
46 cp = compilerop.CachingCompiler()
46 ncache = len(linecache.cache)
47 ncache = len(linecache.cache)
47 cp('x=1', 'single')
48 cp('x=1', 'single')
48 nt.assert_true(len(linecache.cache) > ncache)
49 nt.assert_true(len(linecache.cache) > ncache)
49
50
50 def test_compiler_unicode():
51 def setUp():
51 import sys
52 # Check we're in a proper Python 2 environment (some imports, such
53 # as GTK, can change the default encoding, which can hide bugs.)
52 nt.assert_equal(sys.getdefaultencoding(), "ascii")
54 nt.assert_equal(sys.getdefaultencoding(), "ascii")
53
55
56 def test_compiler_unicode():
54 cp = compilerop.CachingCompiler()
57 cp = compilerop.CachingCompiler()
55 ncache = len(linecache.cache)
58 ncache = len(linecache.cache)
56 cp(u"t = 'žćčőđ'", "single")
59 cp(u"t = 'žćčőđ'", "single")
57 nt.assert_true(len(linecache.cache) > ncache)
60 nt.assert_true(len(linecache.cache) > ncache)
58
61
59 def test_compiler_check_cache():
62 def test_compiler_check_cache():
60 """Test the compiler properly manages the cache.
63 """Test the compiler properly manages the cache.
61 """
64 """
62 # Rather simple-minded tests that just exercise the API
65 # Rather simple-minded tests that just exercise the API
63 cp = compilerop.CachingCompiler()
66 cp = compilerop.CachingCompiler()
64 cp('x=1', 'single', 99)
67 cp('x=1', 'single', 99)
65 # Ensure now that after clearing the cache, our entries survive
68 # Ensure now that after clearing the cache, our entries survive
66 cp.check_cache()
69 cp.check_cache()
67 for k in linecache.cache:
70 for k in linecache.cache:
68 if k.startswith('<ipython-input-99'):
71 if k.startswith('<ipython-input-99'):
69 break
72 break
70 else:
73 else:
71 raise AssertionError('Entry for input-99 missing from linecache')
74 raise AssertionError('Entry for input-99 missing from linecache')
@@ -1,117 +1,119 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the IPython tab-completion machinery.
2 """Tests for the IPython tab-completion machinery.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Module imports
5 # Module imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import os
9 import os
10 import sys
10 import sys
11 import unittest
11 import unittest
12
12
13 # third party
13 # third party
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 # our own packages
16 # our own packages
17 from IPython.utils.tempdir import TemporaryDirectory
17 from IPython.utils.tempdir import TemporaryDirectory
18 from IPython.core.history import HistoryManager, extract_hist_ranges
18 from IPython.core.history import HistoryManager, extract_hist_ranges
19
19
20 def test_history():
20 def setUp():
21 nt.assert_equal(sys.getdefaultencoding(), "ascii")
21 nt.assert_equal(sys.getdefaultencoding(), "ascii")
22
23 def test_history():
22 ip = get_ipython()
24 ip = get_ipython()
23 with TemporaryDirectory() as tmpdir:
25 with TemporaryDirectory() as tmpdir:
24 #tmpdir = '/software/temp'
26 #tmpdir = '/software/temp'
25 histfile = os.path.realpath(os.path.join(tmpdir, 'history.sqlite'))
27 histfile = os.path.realpath(os.path.join(tmpdir, 'history.sqlite'))
26 # Ensure that we restore the history management that we mess with in
28 # Ensure that we restore the history management that we mess with in
27 # this test doesn't affect the IPython instance used by the test suite
29 # this test doesn't affect the IPython instance used by the test suite
28 # beyond this test.
30 # beyond this test.
29 hist_manager_ori = ip.history_manager
31 hist_manager_ori = ip.history_manager
30 try:
32 try:
31 ip.history_manager = HistoryManager(shell=ip)
33 ip.history_manager = HistoryManager(shell=ip)
32 ip.history_manager.hist_file = histfile
34 ip.history_manager.hist_file = histfile
33 ip.history_manager.init_db() # Has to be called after changing file
35 ip.history_manager.init_db() # Has to be called after changing file
34 ip.history_manager.reset()
36 ip.history_manager.reset()
35 print 'test',histfile
37 print 'test',histfile
36 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
38 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
37 for i, h in enumerate(hist, start=1):
39 for i, h in enumerate(hist, start=1):
38 ip.history_manager.store_inputs(i, h)
40 ip.history_manager.store_inputs(i, h)
39
41
40 ip.history_manager.db_log_output = True
42 ip.history_manager.db_log_output = True
41 # Doesn't match the input, but we'll just check it's stored.
43 # Doesn't match the input, but we'll just check it's stored.
42 ip.history_manager.output_hist_reprs[3].append("spam")
44 ip.history_manager.output_hist_reprs[3].append("spam")
43 ip.history_manager.store_output(3)
45 ip.history_manager.store_output(3)
44
46
45 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
47 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
46
48
47 # Check lines were written to DB
49 # Check lines were written to DB
48 c = ip.history_manager.db.execute("SELECT source_raw FROM history")
50 c = ip.history_manager.db.execute("SELECT source_raw FROM history")
49 nt.assert_equal([x for x, in c], hist)
51 nt.assert_equal([x for x, in c], hist)
50
52
51 # New session
53 # New session
52 ip.history_manager.reset()
54 ip.history_manager.reset()
53 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
55 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
54 for i, cmd in enumerate(newcmds, start=1):
56 for i, cmd in enumerate(newcmds, start=1):
55 ip.history_manager.store_inputs(i, cmd)
57 ip.history_manager.store_inputs(i, cmd)
56 gothist = ip.history_manager.get_range(start=1, stop=4)
58 gothist = ip.history_manager.get_range(start=1, stop=4)
57 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
59 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
58 # Previous session:
60 # Previous session:
59 gothist = ip.history_manager.get_range(-1, 1, 4)
61 gothist = ip.history_manager.get_range(-1, 1, 4)
60 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
62 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
61
63
62 # Check get_hist_tail
64 # Check get_hist_tail
63 gothist = ip.history_manager.get_tail(4, output=True,
65 gothist = ip.history_manager.get_tail(4, output=True,
64 include_latest=True)
66 include_latest=True)
65 expected = [(1, 3, (hist[-1], ["spam"])),
67 expected = [(1, 3, (hist[-1], ["spam"])),
66 (2, 1, (newcmds[0], None)),
68 (2, 1, (newcmds[0], None)),
67 (2, 2, (newcmds[1], None)),
69 (2, 2, (newcmds[1], None)),
68 (2, 3, (newcmds[2], None)),]
70 (2, 3, (newcmds[2], None)),]
69 nt.assert_equal(list(gothist), expected)
71 nt.assert_equal(list(gothist), expected)
70
72
71 gothist = ip.history_manager.get_tail(2)
73 gothist = ip.history_manager.get_tail(2)
72 expected = [(2, 1, newcmds[0]),
74 expected = [(2, 1, newcmds[0]),
73 (2, 2, newcmds[1])]
75 (2, 2, newcmds[1])]
74 nt.assert_equal(list(gothist), expected)
76 nt.assert_equal(list(gothist), expected)
75
77
76 # Check get_hist_search
78 # Check get_hist_search
77 gothist = ip.history_manager.search("*test*")
79 gothist = ip.history_manager.search("*test*")
78 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
80 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
79 gothist = ip.history_manager.search("b*", output=True)
81 gothist = ip.history_manager.search("b*", output=True)
80 nt.assert_equal(list(gothist), [(1,3,(hist[2],["spam"]))] )
82 nt.assert_equal(list(gothist), [(1,3,(hist[2],["spam"]))] )
81
83
82 # Cross testing: check that magic %save can get previous session.
84 # Cross testing: check that magic %save can get previous session.
83 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
85 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
84 ip.magic_save(testfilename + " ~1/1-3")
86 ip.magic_save(testfilename + " ~1/1-3")
85 testfile = open(testfilename, "r")
87 testfile = open(testfilename, "r")
86 nt.assert_equal(testfile.read().decode("utf-8"),
88 nt.assert_equal(testfile.read().decode("utf-8"),
87 "# coding: utf-8\n" + "\n".join(hist))
89 "# coding: utf-8\n" + "\n".join(hist))
88
90
89 # Duplicate line numbers - check that it doesn't crash, and
91 # Duplicate line numbers - check that it doesn't crash, and
90 # gets a new session
92 # gets a new session
91 ip.history_manager.store_inputs(1, "rogue")
93 ip.history_manager.store_inputs(1, "rogue")
92 nt.assert_equal(ip.history_manager.session_number, 3)
94 nt.assert_equal(ip.history_manager.session_number, 3)
93 finally:
95 finally:
94 # Restore history manager
96 # Restore history manager
95 ip.history_manager = hist_manager_ori
97 ip.history_manager = hist_manager_ori
96
98
97
99
98 def test_extract_hist_ranges():
100 def test_extract_hist_ranges():
99 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
101 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
100 expected = [(0, 1, 2), # 0 == current session
102 expected = [(0, 1, 2), # 0 == current session
101 (2, 3, 4),
103 (2, 3, 4),
102 (-4, 5, 7),
104 (-4, 5, 7),
103 (-4, 7, 10),
105 (-4, 7, 10),
104 (-9, 2, None), # None == to end
106 (-9, 2, None), # None == to end
105 (-8, 1, None),
107 (-8, 1, None),
106 (-7, 1, 6)]
108 (-7, 1, 6)]
107 actual = list(extract_hist_ranges(instr))
109 actual = list(extract_hist_ranges(instr))
108 nt.assert_equal(actual, expected)
110 nt.assert_equal(actual, expected)
109
111
110 def test_magic_rerun():
112 def test_magic_rerun():
111 """Simple test for %rerun (no args -> rerun last line)"""
113 """Simple test for %rerun (no args -> rerun last line)"""
112 ip = get_ipython()
114 ip = get_ipython()
113 ip.run_cell("a = 10")
115 ip.run_cell("a = 10")
114 ip.run_cell("a += 1")
116 ip.run_cell("a += 1")
115 nt.assert_equal(ip.user_ns["a"], 11)
117 nt.assert_equal(ip.user_ns["a"], 11)
116 ip.run_cell("%rerun")
118 ip.run_cell("%rerun")
117 nt.assert_equal(ip.user_ns["a"], 12)
119 nt.assert_equal(ip.user_ns["a"], 12)
@@ -1,437 +1,441 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) or trial recursively. This
8 calling this script (with different arguments) or trial recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2009 The IPython Development Team
20 # Copyright (C) 2009 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # Stdlib
30 # Stdlib
31 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39
39
40 # Note: monkeypatch!
40 # Note: monkeypatch!
41 # We need to monkeypatch a small problem in nose itself first, before importing
41 # We need to monkeypatch a small problem in nose itself first, before importing
42 # it for actual use. This should get into nose upstream, but its release cycle
42 # it for actual use. This should get into nose upstream, but its release cycle
43 # is slow and we need it for our parametric tests to work correctly.
43 # is slow and we need it for our parametric tests to work correctly.
44 from IPython.testing import nosepatch
44 from IPython.testing import nosepatch
45 # Now, proceed to import nose itself
45 # Now, proceed to import nose itself
46 import nose.plugins.builtin
46 import nose.plugins.builtin
47 from nose.core import TestProgram
47 from nose.core import TestProgram
48
48
49 # Our own imports
49 # Our own imports
50 from IPython.utils.path import get_ipython_module_path
50 from IPython.utils.path import get_ipython_module_path
51 from IPython.utils.process import find_cmd, pycmd2argv
51 from IPython.utils.process import find_cmd, pycmd2argv
52 from IPython.utils.sysinfo import sys_info
52 from IPython.utils.sysinfo import sys_info
53
53
54 from IPython.testing import globalipapp
54 from IPython.testing import globalipapp
55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56
56
57 pjoin = path.join
57 pjoin = path.join
58
58
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Globals
61 # Globals
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Warnings control
66 # Warnings control
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 # Twisted generates annoying warnings with Python 2.6, as will do other code
70 # that imports 'sets' as of today
70 # that imports 'sets' as of today
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
72 DeprecationWarning )
72 DeprecationWarning )
73
73
74 # This one also comes from Twisted
74 # This one also comes from Twisted
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
76 DeprecationWarning)
76 DeprecationWarning)
77
77
78 # Wx on Fedora11 spits these out
78 # Wx on Fedora11 spits these out
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
80 UserWarning)
80 UserWarning)
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Logic for skipping doctests
83 # Logic for skipping doctests
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 def test_for(mod):
86 def test_for(mod):
87 """Test to see if mod is importable."""
87 """Test to see if mod is importable."""
88 try:
88 try:
89 __import__(mod)
89 __import__(mod)
90 except (ImportError, RuntimeError):
90 except (ImportError, RuntimeError):
91 # GTK reports Runtime error if it can't be initialized even if it's
91 # GTK reports Runtime error if it can't be initialized even if it's
92 # importable.
92 # importable.
93 return False
93 return False
94 else:
94 else:
95 return True
95 return True
96
96
97 # Global dict where we can store information on what we have and what we don't
97 # Global dict where we can store information on what we have and what we don't
98 # have available at test run time
98 # have available at test run time
99 have = {}
99 have = {}
100
100
101 have['curses'] = test_for('_curses')
101 have['curses'] = test_for('_curses')
102 have['wx'] = test_for('wx')
102 have['wx'] = test_for('wx')
103 have['wx.aui'] = test_for('wx.aui')
103 have['wx.aui'] = test_for('wx.aui')
104 have['zope.interface'] = test_for('zope.interface')
104 have['zope.interface'] = test_for('zope.interface')
105 have['twisted'] = test_for('twisted')
105 have['twisted'] = test_for('twisted')
106 have['foolscap'] = test_for('foolscap')
106 have['foolscap'] = test_for('foolscap')
107 have['pexpect'] = test_for('pexpect')
107 have['pexpect'] = test_for('pexpect')
108
108
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110 # Functions and classes
110 # Functions and classes
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112
112
113 def report():
113 def report():
114 """Return a string with a summary report of test-related variables."""
114 """Return a string with a summary report of test-related variables."""
115
115
116 out = [ sys_info(), '\n']
116 out = [ sys_info(), '\n']
117
117
118 avail = []
118 avail = []
119 not_avail = []
119 not_avail = []
120
120
121 for k, is_avail in have.items():
121 for k, is_avail in have.items():
122 if is_avail:
122 if is_avail:
123 avail.append(k)
123 avail.append(k)
124 else:
124 else:
125 not_avail.append(k)
125 not_avail.append(k)
126
126
127 if avail:
127 if avail:
128 out.append('\nTools and libraries available at test time:\n')
128 out.append('\nTools and libraries available at test time:\n')
129 avail.sort()
129 avail.sort()
130 out.append(' ' + ' '.join(avail)+'\n')
130 out.append(' ' + ' '.join(avail)+'\n')
131
131
132 if not_avail:
132 if not_avail:
133 out.append('\nTools and libraries NOT available at test time:\n')
133 out.append('\nTools and libraries NOT available at test time:\n')
134 not_avail.sort()
134 not_avail.sort()
135 out.append(' ' + ' '.join(not_avail)+'\n')
135 out.append(' ' + ' '.join(not_avail)+'\n')
136
136
137 return ''.join(out)
137 return ''.join(out)
138
138
139
139
140 def make_exclude():
140 def make_exclude():
141 """Make patterns of modules and packages to exclude from testing.
141 """Make patterns of modules and packages to exclude from testing.
142
142
143 For the IPythonDoctest plugin, we need to exclude certain patterns that
143 For the IPythonDoctest plugin, we need to exclude certain patterns that
144 cause testing problems. We should strive to minimize the number of
144 cause testing problems. We should strive to minimize the number of
145 skipped modules, since this means untested code.
145 skipped modules, since this means untested code.
146
146
147 These modules and packages will NOT get scanned by nose at all for tests.
147 These modules and packages will NOT get scanned by nose at all for tests.
148 """
148 """
149 # Simple utility to make IPython paths more readably, we need a lot of
149 # Simple utility to make IPython paths more readably, we need a lot of
150 # these below
150 # these below
151 ipjoin = lambda *paths: pjoin('IPython', *paths)
151 ipjoin = lambda *paths: pjoin('IPython', *paths)
152
152
153 exclusions = [ipjoin('external'),
153 exclusions = [ipjoin('external'),
154 pjoin('IPython_doctest_plugin'),
154 pjoin('IPython_doctest_plugin'),
155 ipjoin('quarantine'),
155 ipjoin('quarantine'),
156 ipjoin('deathrow'),
156 ipjoin('deathrow'),
157 ipjoin('testing', 'attic'),
157 ipjoin('testing', 'attic'),
158 # This guy is probably attic material
158 # This guy is probably attic material
159 ipjoin('testing', 'mkdoctests'),
159 ipjoin('testing', 'mkdoctests'),
160 # Testing inputhook will need a lot of thought, to figure out
160 # Testing inputhook will need a lot of thought, to figure out
161 # how to have tests that don't lock up with the gui event
161 # how to have tests that don't lock up with the gui event
162 # loops in the picture
162 # loops in the picture
163 ipjoin('lib', 'inputhook'),
163 ipjoin('lib', 'inputhook'),
164 # Config files aren't really importable stand-alone
164 # Config files aren't really importable stand-alone
165 ipjoin('config', 'default'),
165 ipjoin('config', 'default'),
166 ipjoin('config', 'profile'),
166 ipjoin('config', 'profile'),
167 ]
167 ]
168
168
169 if not have['wx']:
169 if not have['wx']:
170 exclusions.append(ipjoin('lib', 'inputhookwx'))
170 exclusions.append(ipjoin('lib', 'inputhookwx'))
171
172 # We do this unconditionally, so that the test suite doesn't import
173 # gtk, changing the default encoding and masking some unicode bugs.
174 exclusions.append(ipjoin('lib', 'inputhookgtk'))
171
175
172 # These have to be skipped on win32 because the use echo, rm, cd, etc.
176 # These have to be skipped on win32 because the use echo, rm, cd, etc.
173 # See ticket https://bugs.launchpad.net/bugs/366982
177 # See ticket https://bugs.launchpad.net/bugs/366982
174 if sys.platform == 'win32':
178 if sys.platform == 'win32':
175 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
179 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
176 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
180 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
177
181
178 if not have['pexpect']:
182 if not have['pexpect']:
179 exclusions.extend([ipjoin('scripts', 'irunner'),
183 exclusions.extend([ipjoin('scripts', 'irunner'),
180 ipjoin('lib', 'irunner')])
184 ipjoin('lib', 'irunner')])
181
185
182 # This is scary. We still have things in frontend and testing that
186 # This is scary. We still have things in frontend and testing that
183 # are being tested by nose that use twisted. We need to rethink
187 # are being tested by nose that use twisted. We need to rethink
184 # how we are isolating dependencies in testing.
188 # how we are isolating dependencies in testing.
185 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
189 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
186 exclusions.extend(
190 exclusions.extend(
187 [ipjoin('testing', 'parametric'),
191 [ipjoin('testing', 'parametric'),
188 ipjoin('testing', 'util'),
192 ipjoin('testing', 'util'),
189 ipjoin('testing', 'tests', 'test_decorators_trial'),
193 ipjoin('testing', 'tests', 'test_decorators_trial'),
190 ] )
194 ] )
191
195
192 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
196 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
193 if sys.platform == 'win32':
197 if sys.platform == 'win32':
194 exclusions = [s.replace('\\','\\\\') for s in exclusions]
198 exclusions = [s.replace('\\','\\\\') for s in exclusions]
195
199
196 return exclusions
200 return exclusions
197
201
198
202
199 class IPTester(object):
203 class IPTester(object):
200 """Call that calls iptest or trial in a subprocess.
204 """Call that calls iptest or trial in a subprocess.
201 """
205 """
202 #: string, name of test runner that will be called
206 #: string, name of test runner that will be called
203 runner = None
207 runner = None
204 #: list, parameters for test runner
208 #: list, parameters for test runner
205 params = None
209 params = None
206 #: list, arguments of system call to be made to call test runner
210 #: list, arguments of system call to be made to call test runner
207 call_args = None
211 call_args = None
208 #: list, process ids of subprocesses we start (for cleanup)
212 #: list, process ids of subprocesses we start (for cleanup)
209 pids = None
213 pids = None
210
214
211 def __init__(self, runner='iptest', params=None):
215 def __init__(self, runner='iptest', params=None):
212 """Create new test runner."""
216 """Create new test runner."""
213 p = os.path
217 p = os.path
214 if runner == 'iptest':
218 if runner == 'iptest':
215 iptest_app = get_ipython_module_path('IPython.testing.iptest')
219 iptest_app = get_ipython_module_path('IPython.testing.iptest')
216 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
220 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
217 elif runner == 'trial':
221 elif runner == 'trial':
218 # For trial, it needs to be installed system-wide
222 # For trial, it needs to be installed system-wide
219 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
223 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
220 else:
224 else:
221 raise Exception('Not a valid test runner: %s' % repr(runner))
225 raise Exception('Not a valid test runner: %s' % repr(runner))
222 if params is None:
226 if params is None:
223 params = []
227 params = []
224 if isinstance(params, str):
228 if isinstance(params, str):
225 params = [params]
229 params = [params]
226 self.params = params
230 self.params = params
227
231
228 # Assemble call
232 # Assemble call
229 self.call_args = self.runner+self.params
233 self.call_args = self.runner+self.params
230
234
231 # Store pids of anything we start to clean up on deletion, if possible
235 # Store pids of anything we start to clean up on deletion, if possible
232 # (on posix only, since win32 has no os.kill)
236 # (on posix only, since win32 has no os.kill)
233 self.pids = []
237 self.pids = []
234
238
235 if sys.platform == 'win32':
239 if sys.platform == 'win32':
236 def _run_cmd(self):
240 def _run_cmd(self):
237 # On Windows, use os.system instead of subprocess.call, because I
241 # On Windows, use os.system instead of subprocess.call, because I
238 # was having problems with subprocess and I just don't know enough
242 # was having problems with subprocess and I just don't know enough
239 # about win32 to debug this reliably. Os.system may be the 'old
243 # about win32 to debug this reliably. Os.system may be the 'old
240 # fashioned' way to do it, but it works just fine. If someone
244 # fashioned' way to do it, but it works just fine. If someone
241 # later can clean this up that's fine, as long as the tests run
245 # later can clean this up that's fine, as long as the tests run
242 # reliably in win32.
246 # reliably in win32.
243 # What types of problems are you having. They may be related to
247 # What types of problems are you having. They may be related to
244 # running Python in unboffered mode. BG.
248 # running Python in unboffered mode. BG.
245 return os.system(' '.join(self.call_args))
249 return os.system(' '.join(self.call_args))
246 else:
250 else:
247 def _run_cmd(self):
251 def _run_cmd(self):
248 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
252 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
249 subp = subprocess.Popen(self.call_args)
253 subp = subprocess.Popen(self.call_args)
250 self.pids.append(subp.pid)
254 self.pids.append(subp.pid)
251 # If this fails, the pid will be left in self.pids and cleaned up
255 # If this fails, the pid will be left in self.pids and cleaned up
252 # later, but if the wait call succeeds, then we can clear the
256 # later, but if the wait call succeeds, then we can clear the
253 # stored pid.
257 # stored pid.
254 retcode = subp.wait()
258 retcode = subp.wait()
255 self.pids.pop()
259 self.pids.pop()
256 return retcode
260 return retcode
257
261
258 def run(self):
262 def run(self):
259 """Run the stored commands"""
263 """Run the stored commands"""
260 try:
264 try:
261 return self._run_cmd()
265 return self._run_cmd()
262 except:
266 except:
263 import traceback
267 import traceback
264 traceback.print_exc()
268 traceback.print_exc()
265 return 1 # signal failure
269 return 1 # signal failure
266
270
267 def __del__(self):
271 def __del__(self):
268 """Cleanup on exit by killing any leftover processes."""
272 """Cleanup on exit by killing any leftover processes."""
269
273
270 if not hasattr(os, 'kill'):
274 if not hasattr(os, 'kill'):
271 return
275 return
272
276
273 for pid in self.pids:
277 for pid in self.pids:
274 try:
278 try:
275 print 'Cleaning stale PID:', pid
279 print 'Cleaning stale PID:', pid
276 os.kill(pid, signal.SIGKILL)
280 os.kill(pid, signal.SIGKILL)
277 except OSError:
281 except OSError:
278 # This is just a best effort, if we fail or the process was
282 # This is just a best effort, if we fail or the process was
279 # really gone, ignore it.
283 # really gone, ignore it.
280 pass
284 pass
281
285
282
286
283 def make_runners():
287 def make_runners():
284 """Define the top-level packages that need to be tested.
288 """Define the top-level packages that need to be tested.
285 """
289 """
286
290
287 # Packages to be tested via nose, that only depend on the stdlib
291 # Packages to be tested via nose, that only depend on the stdlib
288 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
292 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
289 'scripts', 'testing', 'utils' ]
293 'scripts', 'testing', 'utils' ]
290 # The machinery in kernel needs twisted for real testing
294 # The machinery in kernel needs twisted for real testing
291 trial_pkg_names = []
295 trial_pkg_names = []
292
296
293 # And add twisted ones if conditions are met
297 # And add twisted ones if conditions are met
294 if have['zope.interface'] and have['twisted'] and have['foolscap']:
298 if have['zope.interface'] and have['twisted'] and have['foolscap']:
295 # We only list IPython.kernel for testing using twisted.trial as
299 # We only list IPython.kernel for testing using twisted.trial as
296 # nose and twisted.trial have conflicts that make the testing system
300 # nose and twisted.trial have conflicts that make the testing system
297 # unstable.
301 # unstable.
298 trial_pkg_names.append('kernel')
302 trial_pkg_names.append('kernel')
299
303
300 # For debugging this code, only load quick stuff
304 # For debugging this code, only load quick stuff
301 #nose_pkg_names = ['core', 'extensions'] # dbg
305 #nose_pkg_names = ['core', 'extensions'] # dbg
302 #trial_pkg_names = [] # dbg
306 #trial_pkg_names = [] # dbg
303
307
304 # Make fully qualified package names prepending 'IPython.' to our name lists
308 # Make fully qualified package names prepending 'IPython.' to our name lists
305 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
309 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
306 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
310 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
307
311
308 # Make runners
312 # Make runners
309 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
313 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
310 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
314 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
311
315
312 return runners
316 return runners
313
317
314
318
315 def run_iptest():
319 def run_iptest():
316 """Run the IPython test suite using nose.
320 """Run the IPython test suite using nose.
317
321
318 This function is called when this script is **not** called with the form
322 This function is called when this script is **not** called with the form
319 `iptest all`. It simply calls nose with appropriate command line flags
323 `iptest all`. It simply calls nose with appropriate command line flags
320 and accepts all of the standard nose arguments.
324 and accepts all of the standard nose arguments.
321 """
325 """
322
326
323 warnings.filterwarnings('ignore',
327 warnings.filterwarnings('ignore',
324 'This will be removed soon. Use IPython.testing.util instead')
328 'This will be removed soon. Use IPython.testing.util instead')
325
329
326 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
327
331
328 # Loading ipdoctest causes problems with Twisted, but
332 # Loading ipdoctest causes problems with Twisted, but
329 # our test suite runner now separates things and runs
333 # our test suite runner now separates things and runs
330 # all Twisted tests with trial.
334 # all Twisted tests with trial.
331 '--with-ipdoctest',
335 '--with-ipdoctest',
332 '--ipdoctest-tests','--ipdoctest-extension=txt',
336 '--ipdoctest-tests','--ipdoctest-extension=txt',
333
337
334 # We add --exe because of setuptools' imbecility (it
338 # We add --exe because of setuptools' imbecility (it
335 # blindly does chmod +x on ALL files). Nose does the
339 # blindly does chmod +x on ALL files). Nose does the
336 # right thing and it tries to avoid executables,
340 # right thing and it tries to avoid executables,
337 # setuptools unfortunately forces our hand here. This
341 # setuptools unfortunately forces our hand here. This
338 # has been discussed on the distutils list and the
342 # has been discussed on the distutils list and the
339 # setuptools devs refuse to fix this problem!
343 # setuptools devs refuse to fix this problem!
340 '--exe',
344 '--exe',
341 ]
345 ]
342
346
343 if nose.__version__ >= '0.11':
347 if nose.__version__ >= '0.11':
344 # I don't fully understand why we need this one, but depending on what
348 # I don't fully understand why we need this one, but depending on what
345 # directory the test suite is run from, if we don't give it, 0 tests
349 # directory the test suite is run from, if we don't give it, 0 tests
346 # get run. Specifically, if the test suite is run from the source dir
350 # get run. Specifically, if the test suite is run from the source dir
347 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
348 # even if the same call done in this directory works fine). It appears
352 # even if the same call done in this directory works fine). It appears
349 # that if the requested package is in the current dir, nose bails early
353 # that if the requested package is in the current dir, nose bails early
350 # by default. Since it's otherwise harmless, leave it in by default
354 # by default. Since it's otherwise harmless, leave it in by default
351 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
352 argv.append('--traverse-namespace')
356 argv.append('--traverse-namespace')
353
357
354 # Construct list of plugins, omitting the existing doctest plugin, which
358 # Construct list of plugins, omitting the existing doctest plugin, which
355 # ours replaces (and extends).
359 # ours replaces (and extends).
356 plugins = [IPythonDoctest(make_exclude())]
360 plugins = [IPythonDoctest(make_exclude())]
357 for p in nose.plugins.builtin.plugins:
361 for p in nose.plugins.builtin.plugins:
358 plug = p()
362 plug = p()
359 if plug.name == 'doctest':
363 if plug.name == 'doctest':
360 continue
364 continue
361 plugins.append(plug)
365 plugins.append(plug)
362
366
363 # We need a global ipython running in this process
367 # We need a global ipython running in this process
364 globalipapp.start_ipython()
368 globalipapp.start_ipython()
365 # Now nose can run
369 # Now nose can run
366 TestProgram(argv=argv, plugins=plugins)
370 TestProgram(argv=argv, plugins=plugins)
367
371
368
372
369 def run_iptestall():
373 def run_iptestall():
370 """Run the entire IPython test suite by calling nose and trial.
374 """Run the entire IPython test suite by calling nose and trial.
371
375
372 This function constructs :class:`IPTester` instances for all IPython
376 This function constructs :class:`IPTester` instances for all IPython
373 modules and package and then runs each of them. This causes the modules
377 modules and package and then runs each of them. This causes the modules
374 and packages of IPython to be tested each in their own subprocess using
378 and packages of IPython to be tested each in their own subprocess using
375 nose or twisted.trial appropriately.
379 nose or twisted.trial appropriately.
376 """
380 """
377
381
378 runners = make_runners()
382 runners = make_runners()
379
383
380 # Run the test runners in a temporary dir so we can nuke it when finished
384 # Run the test runners in a temporary dir so we can nuke it when finished
381 # to clean up any junk files left over by accident. This also makes it
385 # to clean up any junk files left over by accident. This also makes it
382 # robust against being run in non-writeable directories by mistake, as the
386 # robust against being run in non-writeable directories by mistake, as the
383 # temp dir will always be user-writeable.
387 # temp dir will always be user-writeable.
384 curdir = os.getcwd()
388 curdir = os.getcwd()
385 testdir = tempfile.gettempdir()
389 testdir = tempfile.gettempdir()
386 os.chdir(testdir)
390 os.chdir(testdir)
387
391
388 # Run all test runners, tracking execution time
392 # Run all test runners, tracking execution time
389 failed = []
393 failed = []
390 t_start = time.time()
394 t_start = time.time()
391 try:
395 try:
392 for (name, runner) in runners:
396 for (name, runner) in runners:
393 print '*'*70
397 print '*'*70
394 print 'IPython test group:',name
398 print 'IPython test group:',name
395 res = runner.run()
399 res = runner.run()
396 if res:
400 if res:
397 failed.append( (name, runner) )
401 failed.append( (name, runner) )
398 finally:
402 finally:
399 os.chdir(curdir)
403 os.chdir(curdir)
400 t_end = time.time()
404 t_end = time.time()
401 t_tests = t_end - t_start
405 t_tests = t_end - t_start
402 nrunners = len(runners)
406 nrunners = len(runners)
403 nfail = len(failed)
407 nfail = len(failed)
404 # summarize results
408 # summarize results
405 print
409 print
406 print '*'*70
410 print '*'*70
407 print 'Test suite completed for system with the following information:'
411 print 'Test suite completed for system with the following information:'
408 print report()
412 print report()
409 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
410 print
414 print
411 print 'Status:'
415 print 'Status:'
412 if not failed:
416 if not failed:
413 print 'OK'
417 print 'OK'
414 else:
418 else:
415 # If anything went wrong, point out what command to rerun manually to
419 # If anything went wrong, point out what command to rerun manually to
416 # see the actual errors and individual summary
420 # see the actual errors and individual summary
417 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
418 for name, failed_runner in failed:
422 for name, failed_runner in failed:
419 print '-'*40
423 print '-'*40
420 print 'Runner failed:',name
424 print 'Runner failed:',name
421 print 'You may wish to rerun this one individually, with:'
425 print 'You may wish to rerun this one individually, with:'
422 print ' '.join(failed_runner.call_args)
426 print ' '.join(failed_runner.call_args)
423 print
427 print
424
428
425
429
426 def main():
430 def main():
427 for arg in sys.argv[1:]:
431 for arg in sys.argv[1:]:
428 if arg.startswith('IPython'):
432 if arg.startswith('IPython'):
429 # This is in-process
433 # This is in-process
430 run_iptest()
434 run_iptest()
431 else:
435 else:
432 # This starts subprocesses
436 # This starts subprocesses
433 run_iptestall()
437 run_iptestall()
434
438
435
439
436 if __name__ == '__main__':
440 if __name__ == '__main__':
437 main()
441 main()
General Comments 0
You need to be logged in to leave comments. Login now