##// END OF EJS Templates
More work on the crash handler....
Brian Granger -
Show More
@@ -1,463 +1,449 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 components, passing the config to them.
9 object and then create the components, 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_arguments(self):
73 def _add_arguments(self):
74 self._add_ipython_dir(self.parser)
74 self._add_ipython_dir(self.parser)
75 self._add_log_level(self.parser)
75 self._add_log_level(self.parser)
76
76
77
77
78 class Application(object):
78 class Application(object):
79 """Load a config, construct components and set them running.
79 """Load a config, construct components and set them running.
80
80
81 The configuration of an application can be done via three different Config
81 The configuration of an application can be done via three different Config
82 objects, which are loaded and ultimately merged into a single one used
82 objects, which are loaded and ultimately merged into a single one used
83 from that point on by the app. These are:
83 from that point on by the app. These are:
84
84
85 1. default_config: internal defaults, implemented in code.
85 1. default_config: internal defaults, implemented in code.
86 2. file_config: read from the filesystem.
86 2. file_config: read from the filesystem.
87 3. command_line_config: read from the system's command line flags.
87 3. command_line_config: read from the system's command line flags.
88
88
89 During initialization, 3 is actually read before 2, since at the
89 During initialization, 3 is actually read before 2, since at the
90 command-line one may override the location of the file to be read. But the
90 command-line one may override the location of the file to be read. But the
91 above is the order in which the merge is made.
91 above is the order in which the merge is made.
92 """
92 """
93
93
94 name = u'ipython'
94 name = u'ipython'
95 description = 'IPython: an enhanced interactive Python shell.'
95 description = 'IPython: an enhanced interactive Python shell.'
96 #: Usage message printed by argparse. If None, auto-generate
96 #: Usage message printed by argparse. If None, auto-generate
97 usage = None
97 usage = None
98 #: The command line config loader. Subclass of ArgParseConfigLoader.
98 #: The command line config loader. Subclass of ArgParseConfigLoader.
99 command_line_loader = BaseAppConfigLoader
99 command_line_loader = BaseAppConfigLoader
100 #: The name of the config file to load.
100 #: The name of the config file to load.
101 config_file_name = u'ipython_config.py'
101 config_file_name = u'ipython_config.py'
102 #: The name of the default config file. Track separately from the actual
102 #: The name of the default config file. Track separately from the actual
103 #: name because some logic happens only if we aren't using the default.
103 #: name because some logic happens only if we aren't using the default.
104 default_config_file_name = config_file_name
104 default_config_file_name = config_file_name
105 default_log_level = logging.WARN
105 default_log_level = logging.WARN
106 #: Set by --profile option
106 #: Set by --profile option
107 profile_name = None
107 profile_name = None
108 #: User's ipython directory, typically ~/.ipython/
108 #: User's ipython directory, typically ~/.ipython/
109 ipython_dir = None
109 ipython_dir = None
110 #: Internal defaults, implemented in code.
110 #: Internal defaults, implemented in code.
111 default_config = None
111 default_config = None
112 #: Read from the filesystem.
112 #: Read from the filesystem.
113 file_config = None
113 file_config = None
114 #: Read from the system's command line flags.
114 #: Read from the system's command line flags.
115 command_line_config = None
115 command_line_config = None
116 #: The final config that will be passed to the component.
116 #: The final config that will be passed to the component.
117 master_config = None
117 master_config = None
118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
119 argv = None
119 argv = None
120 #: extra arguments computed by the command-line loader
120 #: extra arguments computed by the command-line loader
121 extra_args = None
121 extra_args = None
122 #: The class to use as the crash handler.
123 crash_handler_class = crashhandler.CrashHandler
122
124
123 # Private attributes
125 # Private attributes
124 _exiting = False
126 _exiting = False
125 _initialized = False
127 _initialized = False
126
128
127 # Class choices for things that will be instantiated at runtime.
128 _CrashHandler = crashhandler.CrashHandler
129
130 def __init__(self, argv=None):
129 def __init__(self, argv=None):
131 self.argv = sys.argv[1:] if argv is None else argv
130 self.argv = sys.argv[1:] if argv is None else argv
132 self.init_logger()
131 self.init_logger()
133
132
134 def init_logger(self):
133 def init_logger(self):
135 self.log = logging.getLogger(self.__class__.__name__)
134 self.log = logging.getLogger(self.__class__.__name__)
136 # This is used as the default until the command line arguments are read.
135 # This is used as the default until the command line arguments are read.
137 self.log.setLevel(self.default_log_level)
136 self.log.setLevel(self.default_log_level)
138 self._log_handler = logging.StreamHandler()
137 self._log_handler = logging.StreamHandler()
139 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
138 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
140 self._log_handler.setFormatter(self._log_formatter)
139 self._log_handler.setFormatter(self._log_formatter)
141 self.log.addHandler(self._log_handler)
140 self.log.addHandler(self._log_handler)
142
141
143 def _set_log_level(self, level):
142 def _set_log_level(self, level):
144 self.log.setLevel(level)
143 self.log.setLevel(level)
145
144
146 def _get_log_level(self):
145 def _get_log_level(self):
147 return self.log.level
146 return self.log.level
148
147
149 log_level = property(_get_log_level, _set_log_level)
148 log_level = property(_get_log_level, _set_log_level)
150
149
151 def initialize(self):
150 def initialize(self):
152 """Initialize the application.
151 """Initialize the application.
153
152
154 Loads all configuration information and sets all application state, but
153 Loads all configuration information and sets all application state, but
155 does not start any relevant processing (typically some kind of event
154 does not start any relevant processing (typically some kind of event
156 loop).
155 loop).
157
156
158 Once this method has been called, the application is flagged as
157 Once this method has been called, the application is flagged as
159 initialized and the method becomes a no-op."""
158 initialized and the method becomes a no-op."""
160
159
161 if self._initialized:
160 if self._initialized:
162 return
161 return
163
162
164 # The first part is protected with an 'attempt' wrapper, that will log
163 # The first part is protected with an 'attempt' wrapper, that will log
165 # failures with the basic system traceback machinery. Once our crash
164 # failures with the basic system traceback machinery. Once our crash
166 # handler is in place, we can let any subsequent exception propagate,
165 # handler is in place, we can let any subsequent exception propagate,
167 # as our handler will log it with much better detail than the default.
166 # as our handler will log it with much better detail than the default.
168 self.attempt(self.create_crash_handler)
167 self.attempt(self.create_crash_handler)
169
168
170 # Configuration phase
169 # Configuration phase
171 # Default config (internally hardwired in application code)
170 # Default config (internally hardwired in application code)
172 self.create_default_config()
171 self.create_default_config()
173 self.log_default_config()
172 self.log_default_config()
174 self.set_default_config_log_level()
173 self.set_default_config_log_level()
175
174
176 # Command-line config
175 # Command-line config
177 self.pre_load_command_line_config()
176 self.pre_load_command_line_config()
178 self.load_command_line_config()
177 self.load_command_line_config()
179 self.set_command_line_config_log_level()
178 self.set_command_line_config_log_level()
180 self.post_load_command_line_config()
179 self.post_load_command_line_config()
181 self.log_command_line_config()
180 self.log_command_line_config()
182
181
183 # Find resources needed for filesystem access, using information from
182 # Find resources needed for filesystem access, using information from
184 # the above two
183 # the above two
185 self.find_ipython_dir()
184 self.find_ipython_dir()
186 self.find_resources()
185 self.find_resources()
187 self.find_config_file_name()
186 self.find_config_file_name()
188 self.find_config_file_paths()
187 self.find_config_file_paths()
189
188
190 # File-based config
189 # File-based config
191 self.pre_load_file_config()
190 self.pre_load_file_config()
192 self.load_file_config()
191 self.load_file_config()
193 self.set_file_config_log_level()
192 self.set_file_config_log_level()
194 self.post_load_file_config()
193 self.post_load_file_config()
195 self.log_file_config()
194 self.log_file_config()
196
195
197 # Merge all config objects into a single one the app can then use
196 # Merge all config objects into a single one the app can then use
198 self.merge_configs()
197 self.merge_configs()
199 self.log_master_config()
198 self.log_master_config()
200
199
201 # Construction phase
200 # Construction phase
202 self.pre_construct()
201 self.pre_construct()
203 self.construct()
202 self.construct()
204 self.post_construct()
203 self.post_construct()
205
204
206 # Done, flag as such and
205 # Done, flag as such and
207 self._initialized = True
206 self._initialized = True
208
207
209 def start(self):
208 def start(self):
210 """Start the application."""
209 """Start the application."""
211 self.initialize()
210 self.initialize()
212 self.start_app()
211 self.start_app()
213
212
214 #-------------------------------------------------------------------------
213 #-------------------------------------------------------------------------
215 # Various stages of Application creation
214 # Various stages of Application creation
216 #-------------------------------------------------------------------------
215 #-------------------------------------------------------------------------
217
216
218 def create_crash_handler(self):
217 def create_crash_handler(self):
219 """Create a crash handler, typically setting sys.excepthook to it."""
218 """Create a crash handler, typically setting sys.excepthook to it."""
220 self.crash_handler = self._CrashHandler(self, self.name)
219 self.crash_handler = self.crash_handler_class(self)
221 sys.excepthook = self.crash_handler
220 sys.excepthook = self.crash_handler
222
221
223 def create_default_config(self):
222 def create_default_config(self):
224 """Create defaults that can't be set elsewhere.
223 """Create defaults that can't be set elsewhere.
225
224
226 For the most part, we try to set default in the class attributes
225 For the most part, we try to set default in the class attributes
227 of Components. But, defaults the top-level Application (which is
226 of Components. But, defaults the top-level Application (which is
228 not a HasTraitlets or Component) are not set in this way. Instead
227 not a HasTraitlets or Component) are not set in this way. Instead
229 we set them here. The Global section is for variables like this that
228 we set them here. The Global section is for variables like this that
230 don't belong to a particular component.
229 don't belong to a particular component.
231 """
230 """
232 c = Config()
231 c = Config()
233 c.Global.ipython_dir = get_ipython_dir()
232 c.Global.ipython_dir = get_ipython_dir()
234 c.Global.log_level = self.log_level
233 c.Global.log_level = self.log_level
235 self.default_config = c
234 self.default_config = c
236
235
237 def log_default_config(self):
236 def log_default_config(self):
238 self.log.debug('Default config loaded:')
237 self.log.debug('Default config loaded:')
239 self.log.debug(repr(self.default_config))
238 self.log.debug(repr(self.default_config))
240
239
241 def set_default_config_log_level(self):
240 def set_default_config_log_level(self):
242 try:
241 try:
243 self.log_level = self.default_config.Global.log_level
242 self.log_level = self.default_config.Global.log_level
244 except AttributeError:
243 except AttributeError:
245 # Fallback to the default_log_level class attribute
244 # Fallback to the default_log_level class attribute
246 pass
245 pass
247
246
248 def create_command_line_config(self):
247 def create_command_line_config(self):
249 """Create and return a command line config loader."""
248 """Create and return a command line config loader."""
250 return self.command_line_loader(
249 return self.command_line_loader(
251 self.argv,
250 self.argv,
252 description=self.description,
251 description=self.description,
253 version=release.version,
252 version=release.version,
254 usage=self.usage
253 usage=self.usage
255 )
254 )
256
255
257 def pre_load_command_line_config(self):
256 def pre_load_command_line_config(self):
258 """Do actions just before loading the command line config."""
257 """Do actions just before loading the command line config."""
259 pass
258 pass
260
259
261 def load_command_line_config(self):
260 def load_command_line_config(self):
262 """Load the command line config."""
261 """Load the command line config."""
263 loader = self.create_command_line_config()
262 loader = self.create_command_line_config()
264 self.command_line_config = loader.load_config()
263 self.command_line_config = loader.load_config()
265 self.extra_args = loader.get_extra_args()
264 self.extra_args = loader.get_extra_args()
266
265
267 def set_command_line_config_log_level(self):
266 def set_command_line_config_log_level(self):
268 try:
267 try:
269 self.log_level = self.command_line_config.Global.log_level
268 self.log_level = self.command_line_config.Global.log_level
270 except AttributeError:
269 except AttributeError:
271 pass
270 pass
272
271
273 def post_load_command_line_config(self):
272 def post_load_command_line_config(self):
274 """Do actions just after loading the command line config."""
273 """Do actions just after loading the command line config."""
275 pass
274 pass
276
275
277 def log_command_line_config(self):
276 def log_command_line_config(self):
278 self.log.debug("Command line config loaded:")
277 self.log.debug("Command line config loaded:")
279 self.log.debug(repr(self.command_line_config))
278 self.log.debug(repr(self.command_line_config))
280
279
281 def find_ipython_dir(self):
280 def find_ipython_dir(self):
282 """Set the IPython directory.
281 """Set the IPython directory.
283
282
284 This sets ``self.ipython_dir``, but the actual value that is passed to
283 This sets ``self.ipython_dir``, but the actual value that is passed to
285 the application is kept in either ``self.default_config`` or
284 the application is kept in either ``self.default_config`` or
286 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
285 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
287 ``sys.path`` so config files there can be referenced by other config
286 ``sys.path`` so config files there can be referenced by other config
288 files.
287 files.
289 """
288 """
290
289
291 try:
290 try:
292 self.ipython_dir = self.command_line_config.Global.ipython_dir
291 self.ipython_dir = self.command_line_config.Global.ipython_dir
293 except AttributeError:
292 except AttributeError:
294 self.ipython_dir = self.default_config.Global.ipython_dir
293 self.ipython_dir = self.default_config.Global.ipython_dir
295 sys.path.append(os.path.abspath(self.ipython_dir))
294 sys.path.append(os.path.abspath(self.ipython_dir))
296 if not os.path.isdir(self.ipython_dir):
295 if not os.path.isdir(self.ipython_dir):
297 os.makedirs(self.ipython_dir, mode=0777)
296 os.makedirs(self.ipython_dir, mode=0777)
298 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
297 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
299
298
300 def find_resources(self):
299 def find_resources(self):
301 """Find other resources that need to be in place.
300 """Find other resources that need to be in place.
302
301
303 Things like cluster directories need to be in place to find the
302 Things like cluster directories need to be in place to find the
304 config file. These happen right after the IPython directory has
303 config file. These happen right after the IPython directory has
305 been set.
304 been set.
306 """
305 """
307 pass
306 pass
308
307
309 def find_config_file_name(self):
308 def find_config_file_name(self):
310 """Find the config file name for this application.
309 """Find the config file name for this application.
311
310
312 This must set ``self.config_file_name`` to the filename of the
311 This must set ``self.config_file_name`` to the filename of the
313 config file to use (just the filename). The search paths for the
312 config file to use (just the filename). The search paths for the
314 config file are set in :meth:`find_config_file_paths` and then passed
313 config file are set in :meth:`find_config_file_paths` and then passed
315 to the config file loader where they are resolved to an absolute path.
314 to the config file loader where they are resolved to an absolute path.
316
315
317 If a profile has been set at the command line, this will resolve it.
316 If a profile has been set at the command line, this will resolve it.
318 """
317 """
319
318
320 try:
319 try:
321 self.config_file_name = self.command_line_config.Global.config_file
320 self.config_file_name = self.command_line_config.Global.config_file
322 except AttributeError:
321 except AttributeError:
323 pass
322 pass
324
323
325 try:
324 try:
326 self.profile_name = self.command_line_config.Global.profile
325 self.profile_name = self.command_line_config.Global.profile
327 except AttributeError:
326 except AttributeError:
328 pass
327 pass
329 else:
328 else:
330 name_parts = self.config_file_name.split('.')
329 name_parts = self.config_file_name.split('.')
331 name_parts.insert(1, u'_' + self.profile_name + u'.')
330 name_parts.insert(1, u'_' + self.profile_name + u'.')
332 self.config_file_name = ''.join(name_parts)
331 self.config_file_name = ''.join(name_parts)
333
332
334 def find_config_file_paths(self):
333 def find_config_file_paths(self):
335 """Set the search paths for resolving the config file.
334 """Set the search paths for resolving the config file.
336
335
337 This must set ``self.config_file_paths`` to a sequence of search
336 This must set ``self.config_file_paths`` to a sequence of search
338 paths to pass to the config file loader.
337 paths to pass to the config file loader.
339 """
338 """
340 # Include our own profiles directory last, so that users can still find
339 # Include our own profiles directory last, so that users can still find
341 # our shipped copies of builtin profiles even if they don't have them
340 # our shipped copies of builtin profiles even if they don't have them
342 # in their local ipython directory.
341 # in their local ipython directory.
343 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
342 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
344 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
343 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
345
344
346 def pre_load_file_config(self):
345 def pre_load_file_config(self):
347 """Do actions before the config file is loaded."""
346 """Do actions before the config file is loaded."""
348 pass
347 pass
349
348
350 def load_file_config(self):
349 def load_file_config(self):
351 """Load the config file.
350 """Load the config file.
352
351
353 This tries to load the config file from disk. If successful, the
352 This tries to load the config file from disk. If successful, the
354 ``CONFIG_FILE`` config variable is set to the resolved config file
353 ``CONFIG_FILE`` config variable is set to the resolved config file
355 location. If not successful, an empty config is used.
354 location. If not successful, an empty config is used.
356 """
355 """
357 self.log.debug("Attempting to load config file: %s" %
356 self.log.debug("Attempting to load config file: %s" %
358 self.config_file_name)
357 self.config_file_name)
359 loader = PyFileConfigLoader(self.config_file_name,
358 loader = PyFileConfigLoader(self.config_file_name,
360 path=self.config_file_paths)
359 path=self.config_file_paths)
361 try:
360 try:
362 self.file_config = loader.load_config()
361 self.file_config = loader.load_config()
363 self.file_config.Global.config_file = loader.full_filename
362 self.file_config.Global.config_file = loader.full_filename
364 except IOError:
363 except IOError:
365 # Only warn if the default config file was NOT being used.
364 # Only warn if the default config file was NOT being used.
366 if not self.config_file_name==self.default_config_file_name:
365 if not self.config_file_name==self.default_config_file_name:
367 self.log.warn("Config file not found, skipping: %s" %
366 self.log.warn("Config file not found, skipping: %s" %
368 self.config_file_name, exc_info=True)
367 self.config_file_name, exc_info=True)
369 self.file_config = Config()
368 self.file_config = Config()
370 except:
369 except:
371 self.log.warn("Error loading config file: %s" %
370 self.log.warn("Error loading config file: %s" %
372 self.config_file_name, exc_info=True)
371 self.config_file_name, exc_info=True)
373 self.file_config = Config()
372 self.file_config = Config()
374
373
375 def set_file_config_log_level(self):
374 def set_file_config_log_level(self):
376 # We need to keeep self.log_level updated. But we only use the value
375 # We need to keeep self.log_level updated. But we only use the value
377 # of the file_config if a value was not specified at the command
376 # of the file_config if a value was not specified at the command
378 # line, because the command line overrides everything.
377 # line, because the command line overrides everything.
379 if not hasattr(self.command_line_config.Global, 'log_level'):
378 if not hasattr(self.command_line_config.Global, 'log_level'):
380 try:
379 try:
381 self.log_level = self.file_config.Global.log_level
380 self.log_level = self.file_config.Global.log_level
382 except AttributeError:
381 except AttributeError:
383 pass # Use existing value
382 pass # Use existing value
384
383
385 def post_load_file_config(self):
384 def post_load_file_config(self):
386 """Do actions after the config file is loaded."""
385 """Do actions after the config file is loaded."""
387 pass
386 pass
388
387
389 def log_file_config(self):
388 def log_file_config(self):
390 if hasattr(self.file_config.Global, 'config_file'):
389 if hasattr(self.file_config.Global, 'config_file'):
391 self.log.debug("Config file loaded: %s" %
390 self.log.debug("Config file loaded: %s" %
392 self.file_config.Global.config_file)
391 self.file_config.Global.config_file)
393 self.log.debug(repr(self.file_config))
392 self.log.debug(repr(self.file_config))
394
393
395 def merge_configs(self):
394 def merge_configs(self):
396 """Merge the default, command line and file config objects."""
395 """Merge the default, command line and file config objects."""
397 config = Config()
396 config = Config()
398 config._merge(self.default_config)
397 config._merge(self.default_config)
399 config._merge(self.file_config)
398 config._merge(self.file_config)
400 config._merge(self.command_line_config)
399 config._merge(self.command_line_config)
401
400
402 # XXX fperez - propose to Brian we rename master_config to simply
401 # XXX fperez - propose to Brian we rename master_config to simply
403 # config, I think this is going to be heavily used in examples and
402 # config, I think this is going to be heavily used in examples and
404 # application code and the name is shorter/easier to find/remember.
403 # application code and the name is shorter/easier to find/remember.
405 # For now, just alias it...
404 # For now, just alias it...
406 self.master_config = config
405 self.master_config = config
407 self.config = config
406 self.config = config
408
407
409 def log_master_config(self):
408 def log_master_config(self):
410 self.log.debug("Master config created:")
409 self.log.debug("Master config created:")
411 self.log.debug(repr(self.master_config))
410 self.log.debug(repr(self.master_config))
412
411
413 def pre_construct(self):
412 def pre_construct(self):
414 """Do actions after the config has been built, but before construct."""
413 """Do actions after the config has been built, but before construct."""
415 pass
414 pass
416
415
417 def construct(self):
416 def construct(self):
418 """Construct the main components that make up this app."""
417 """Construct the main components that make up this app."""
419 self.log.debug("Constructing components for application")
418 self.log.debug("Constructing components for application")
420
419
421 def post_construct(self):
420 def post_construct(self):
422 """Do actions after construct, but before starting the app."""
421 """Do actions after construct, but before starting the app."""
423 pass
422 pass
424
423
425 def start_app(self):
424 def start_app(self):
426 """Actually start the app."""
425 """Actually start the app."""
427 self.log.debug("Starting application")
426 self.log.debug("Starting application")
428
427
429 #-------------------------------------------------------------------------
428 #-------------------------------------------------------------------------
430 # Utility methods
429 # Utility methods
431 #-------------------------------------------------------------------------
430 #-------------------------------------------------------------------------
432
431
433 def abort(self):
434 """Abort the starting of the application."""
435 if self._exiting:
436 pass
437 else:
438 self.log.critical("Aborting application: %s" % self.name, exc_info=True)
439 self._exiting = True
440 sys.exit(1)
441
442 def exit(self, exit_status=0):
432 def exit(self, exit_status=0):
443 if self._exiting:
433 if self._exiting:
444 pass
434 pass
445 else:
435 else:
446 self.log.debug("Exiting application: %s" % self.name)
436 self.log.debug("Exiting application: %s" % self.name)
447 self._exiting = True
437 self._exiting = True
448 sys.exit(exit_status)
438 sys.exit(exit_status)
449
439
450 def attempt(self, func, action='abort'):
440 def attempt(self, func):
451 try:
441 try:
452 func()
442 func()
453 except SystemExit:
443 except SystemExit:
454 raise
444 raise
455 except:
445 except:
456 if action == 'abort':
446 self.log.critical("Aborting application: %s" % self.name,
457 self.log.critical("Aborting application: %s" % self.name,
447 exc_info=True)
458 exc_info=True)
448 self.exit(0)
459 self.abort()
460 raise
461 elif action == 'exit':
462 self.exit(0)
463
449
@@ -1,224 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # encoding: utf-8
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
3
4 Authors:
4
5
5 Authors
6 * Fernando Perez
6 -------
7 * Brian E. Granger
7 - Fernando Perez <Fernando.Perez@berkeley.edu>
8 """
8 """
9
9
10 #*****************************************************************************
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2009 The IPython Development Team
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 # Copyright (C) 2008-2010 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 # Required modules
19 # Imports
20 #-----------------------------------------------------------------------------
20
21
21 # From the standard library
22 import os
22 import os
23 import sys
23 import sys
24 from pprint import pformat
24 from pprint import pformat
25
25
26 # Our own
27 from IPython.core import release
28 from IPython.core import ultratb
26 from IPython.core import ultratb
27 from IPython.external.Itpl import itpl
29 from IPython.utils.sysinfo import sys_info
28 from IPython.utils.sysinfo import sys_info
30
29
31 from IPython.external.Itpl import itpl
30 #-----------------------------------------------------------------------------
31 # Code
32 #-----------------------------------------------------------------------------
32
33
33 #****************************************************************************
34 # Template for the user message.
35 _default_message_template = """\
36 Oops, $self.app_name crashed. We do our best to make it stable, but...
34
37
35 class CrashHandler(object):
38 A crash report was automatically generated with the following information:
36 """Customizable crash handlers for IPython-based systems.
39 - A verbatim copy of the crash traceback.
40 - A copy of your input history during this session.
41 - Data on your current $self.app_name configuration.
37
42
38 Instances of this class provide a __call__ method which can be used as a
43 It was left in the file named:
39 sys.excepthook, i.e., the __call__ signature is:
44 \t'$self.crash_report_fname'
45 If you can email this file to the developers, the information in it will help
46 them in understanding and correcting the problem.
40
47
41 def __call__(self,etype, evalue, etb)
48 You can mail it to: $self.contact_name at $self.contact_email
49 with the subject '$self.app_name Crash Report'.
42
50
43 """
51 If you want to do it now, the following command will work (under Unix):
52 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
53
54 To ensure accurate tracking of this issue, please file a report about it at:
55 $self.bug_tracker
56 """
44
57
45 def __init__(self,app, app_name, contact_name=None, contact_email=None,
46 bug_tracker=None, crash_report_fname='CrashReport.txt',
47 show_crash_traceback=True, call_pdb=False):
48 """New crash handler.
49
58
50 Inputs:
59 class CrashHandler(object):
60 """Customizable crash handlers for IPython applications.
51
61
52 - app: a running application instance, which will be queried at crash
62 Instances of this class provide a :meth:`__call__` method which can be
53 time for internal information.
63 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
54
64
55 - app_name: a string containing the name of your application.
65 def __call__(self, etype, evalue, etb)
66 """
56
67
57 - contact_name: a string with the name of the person to contact.
68 message_template = _default_message_template
58
69
59 - contact_email: a string with the email address of the contact.
70 def __init__(self, app, contact_name=None, contact_email=None,
71 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
72 """Create a new crash handler
60
73
61 - bug_tracker: a string with the URL for your project's bug tracker.
74 Parameters
75 ----------
76 app : Application
77 A running :class:`Application` instance, which will be queried at
78 crash time for internal information.
62
79
63 - crash_report_fname: a string with the filename for the crash report
80 contact_name : str
64 to be saved in. These reports are left in the ipython user directory
81 A string with the name of the person to contact.
65 as determined by the running IPython instance.
66
82
67 Optional inputs:
83 contact_email : str
68
84 A string with the email address of the contact.
69 - show_crash_traceback(True): if false, don't print the crash
70 traceback on stderr, only generate the on-disk report
71
85
86 bug_tracker : str
87 A string with the URL for your project's bug tracker.
88
89 show_crash_traceback : bool
90 If false, don't print the crash traceback on stderr, only generate
91 the on-disk report
72
92
73 Non-argument instance attributes:
93 Non-argument instance attributes:
74
94
75 These instances contain some non-argument attributes which allow for
95 These instances contain some non-argument attributes which allow for
76 further customization of the crash handler's behavior. Please see the
96 further customization of the crash handler's behavior. Please see the
77 source for further details.
97 source for further details.
78 """
98 """
79
80 # apply args into instance
81 self.app = app
99 self.app = app
82 self.app_name = app_name
100 self.app_name = self.app.name
83 self.contact_name = contact_name
101 self.contact_name = contact_name
84 self.contact_email = contact_email
102 self.contact_email = contact_email
85 self.bug_tracker = bug_tracker
103 self.bug_tracker = bug_tracker
86 self.crash_report_fname = crash_report_fname
104 self.crash_report_fname = "Crash_report_%s.txt" % self.app_name
87 self.show_crash_traceback = show_crash_traceback
105 self.show_crash_traceback = show_crash_traceback
88 self.section_sep = '\n\n'+'*'*75+'\n\n'
106 self.section_sep = '\n\n'+'*'*75+'\n\n'
89 self.call_pdb = call_pdb
107 self.call_pdb = call_pdb
90 #self.call_pdb = True # dbg
108 #self.call_pdb = True # dbg
91
92 # Hardcoded defaults, which can be overridden either by subclasses or
93 # at runtime for the instance.
94
95 # Template for the user message. Subclasses which completely override
96 # this, or user apps, can modify it to suit their tastes. It gets
97 # expanded using itpl, so calls of the kind $self.foo are valid.
98 self.user_message_template = """
99 Oops, $self.app_name crashed. We do our best to make it stable, but...
100
101 A crash report was automatically generated with the following information:
102 - A verbatim copy of the crash traceback.
103 - A copy of your input history during this session.
104 - Data on your current $self.app_name configuration.
105
106 It was left in the file named:
107 \t'$self.crash_report_fname'
108 If you can email this file to the developers, the information in it will help
109 them in understanding and correcting the problem.
110
111 You can mail it to: $self.contact_name at $self.contact_email
112 with the subject '$self.app_name Crash Report'.
113
114 If you want to do it now, the following command will work (under Unix):
115 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
116
117 To ensure accurate tracking of this issue, please file a report about it at:
118 $self.bug_tracker
119 """
120
109
121 def __call__(self,etype, evalue, etb):
110 def __call__(self, etype, evalue, etb):
122 """Handle an exception, call for compatible with sys.excepthook"""
111 """Handle an exception, call for compatible with sys.excepthook"""
123
112
124 # Report tracebacks shouldn't use color in general (safer for users)
113 # Report tracebacks shouldn't use color in general (safer for users)
125 color_scheme = 'NoColor'
114 color_scheme = 'NoColor'
126
115
127 # Use this ONLY for developer debugging (keep commented out for release)
116 # Use this ONLY for developer debugging (keep commented out for release)
128 #color_scheme = 'Linux' # dbg
117 #color_scheme = 'Linux' # dbg
129
118
130 try:
119 try:
131 rptdir = self.app.ipython_dir
120 rptdir = self.app.ipython_dir
132 except:
121 except:
133 rptdir = os.getcwd()
122 rptdir = os.getcwd()
134 if not os.path.isdir(rptdir):
123 if not os.path.isdir(rptdir):
135 rptdir = os.getcwd()
124 rptdir = os.getcwd()
136 report_name = os.path.join(rptdir,self.crash_report_fname)
125 report_name = os.path.join(rptdir,self.crash_report_fname)
137 # write the report filename into the instance dict so it can get
126 # write the report filename into the instance dict so it can get
138 # properly expanded out in the user message template
127 # properly expanded out in the user message template
139 self.crash_report_fname = report_name
128 self.crash_report_fname = report_name
140 TBhandler = ultratb.VerboseTB(color_scheme=color_scheme,
129 TBhandler = ultratb.VerboseTB(
141 long_header=1,
130 color_scheme=color_scheme,
142 call_pdb=self.call_pdb,
131 long_header=1,
143 )
132 call_pdb=self.call_pdb,
133 )
144 if self.call_pdb:
134 if self.call_pdb:
145 TBhandler(etype,evalue,etb)
135 TBhandler(etype,evalue,etb)
146 return
136 return
147 else:
137 else:
148 traceback = TBhandler.text(etype,evalue,etb,context=31)
138 traceback = TBhandler.text(etype,evalue,etb,context=31)
149
139
150 # print traceback to screen
140 # print traceback to screen
151 if self.show_crash_traceback:
141 if self.show_crash_traceback:
152 print >> sys.stderr, traceback
142 print >> sys.stderr, traceback
153
143
154 # and generate a complete report on disk
144 # and generate a complete report on disk
155 try:
145 try:
156 report = open(report_name,'w')
146 report = open(report_name,'w')
157 except:
147 except:
158 print >> sys.stderr, 'Could not create crash report on disk.'
148 print >> sys.stderr, 'Could not create crash report on disk.'
159 return
149 return
160
150
161 # Inform user on stderr of what happened
151 # Inform user on stderr of what happened
162 msg = itpl('\n'+'*'*70+'\n'+self.user_message_template)
152 msg = itpl('\n'+'*'*70+'\n'+self.message_template)
163 print >> sys.stderr, msg
153 print >> sys.stderr, msg
164
154
165 # Construct report on disk
155 # Construct report on disk
166 report.write(self.make_report(traceback))
156 report.write(self.make_report(traceback))
167 report.close()
157 report.close()
168 raw_input("Hit <Enter> to quit this message (your terminal may close):")
158 raw_input("Hit <Enter> to quit this message (your terminal may close):")
169
159
170 def make_report(self,traceback):
160 def make_report(self,traceback):
171 """Return a string containing a crash report."""
161 """Return a string containing a crash report."""
172
162
173 sec_sep = self.section_sep
163 sec_sep = self.section_sep
174
164
175 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
165 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
176 rpt_add = report.append
166 rpt_add = report.append
177 rpt_add(sys_info())
167 rpt_add(sys_info())
178
168
179 try:
169 try:
180 config = pformat(self.app.config)
170 config = pformat(self.app.config)
181 rpt_add(sec_sep+'Current user configuration structure:\n\n')
171 rpt_add(sec_sep)
172 rpt_add('Application name: %s\n\n' % self.app_name)
173 rpt_add('Current user configuration structure:\n\n')
182 rpt_add(config)
174 rpt_add(config)
183 except:
175 except:
184 pass
176 pass
185 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
177 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
186
178
187 return ''.join(report)
179 return ''.join(report)
188
180
189
190 class IPythonCrashHandler(CrashHandler):
191 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
192
193 def __init__(self, app, app_name='IPython'):
194
195 # Set here which of the IPython authors should be listed as contact
196 AUTHOR_CONTACT = 'Fernando'
197
198 # Set argument defaults
199 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
200 contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2]
201 crash_report_fname = 'IPython_crash_report.txt'
202 # Call parent constructor
203 CrashHandler.__init__(self,app,app_name,contact_name,contact_email,
204 bug_tracker,crash_report_fname)
205
206 def make_report(self,traceback):
207 """Return a string containing a crash report."""
208
209 sec_sep = self.section_sep
210 # Start with parent report
211 report = [super(IPythonCrashHandler, self).make_report(traceback)]
212 # Add interactive-specific info we may have
213 rpt_add = report.append
214 try:
215 rpt_add(sec_sep+"History of session input:")
216 for line in self.app.shell.user_ns['_ih']:
217 rpt_add(line)
218 rpt_add('\n*** Last line of input (may not be in above history):\n')
219 rpt_add(self.app.shell._last_input_line+'\n')
220 except:
221 pass
222
223 return ''.join(report)
224
@@ -1,590 +1,651 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 """
12 """
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008-2010 The IPython Development Team
15 # Copyright (C) 2008-2010 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24 from __future__ import absolute_import
25 from __future__ import absolute_import
25
26
26 import logging
27 import logging
27 import os
28 import os
28 import sys
29 import sys
29
30
30 from IPython.core import crashhandler
31 from IPython.core import release
32 from IPython.core.crashhandler import CrashHandler
31 from IPython.core.application import Application, BaseAppConfigLoader
33 from IPython.core.application import Application, BaseAppConfigLoader
32 from IPython.core.iplib import InteractiveShell
34 from IPython.core.iplib import InteractiveShell
33 from IPython.config.loader import (
35 from IPython.config.loader import (
34 Config,
36 Config,
35 PyFileConfigLoader
37 PyFileConfigLoader
36 )
38 )
37 from IPython.lib import inputhook
39 from IPython.lib import inputhook
38 from IPython.utils.path import filefind, get_ipython_dir
40 from IPython.utils.path import filefind, get_ipython_dir
39 from . import usage
41 from . import usage
40
42
41 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
42 # Globals, utilities and helpers
44 # Globals, utilities and helpers
43 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
44
46
45 #: The default config file name for this application.
47 #: The default config file name for this application.
46 default_config_file_name = u'ipython_config.py'
48 default_config_file_name = u'ipython_config.py'
47
49
48
50
49 class IPAppConfigLoader(BaseAppConfigLoader):
51 class IPAppConfigLoader(BaseAppConfigLoader):
50
52
51 def _add_arguments(self):
53 def _add_arguments(self):
52 super(IPAppConfigLoader, self)._add_arguments()
54 super(IPAppConfigLoader, self)._add_arguments()
53 paa = self.parser.add_argument
55 paa = self.parser.add_argument
54 paa('-p',
56 paa('-p',
55 '--profile', dest='Global.profile', type=unicode,
57 '--profile', dest='Global.profile', type=unicode,
56 help=
58 help=
57 """The string name of the ipython profile to be used. Assume that your
59 """The string name of the ipython profile to be used. Assume that your
58 config file is ipython_config-<name>.py (looks in current dir first,
60 config file is ipython_config-<name>.py (looks in current dir first,
59 then in IPYTHON_DIR). This is a quick way to keep and load multiple
61 then in IPYTHON_DIR). This is a quick way to keep and load multiple
60 config files for different tasks, especially if include your basic one
62 config files for different tasks, especially if include your basic one
61 in your more specialized ones. You can keep a basic
63 in your more specialized ones. You can keep a basic
62 IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
64 IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
63 include this one and load extra things for particular tasks.""",
65 include this one and load extra things for particular tasks.""",
64 metavar='Global.profile')
66 metavar='Global.profile')
65 paa('--config-file',
67 paa('--config-file',
66 dest='Global.config_file', type=unicode,
68 dest='Global.config_file', type=unicode,
67 help=
69 help=
68 """Set the config file name to override default. Normally IPython
70 """Set the config file name to override default. Normally IPython
69 loads ipython_config.py (from current directory) or
71 loads ipython_config.py (from current directory) or
70 IPYTHON_DIR/ipython_config.py. If the loading of your config file
72 IPYTHON_DIR/ipython_config.py. If the loading of your config file
71 fails, IPython starts with a bare bones configuration (no modules
73 fails, IPython starts with a bare bones configuration (no modules
72 loaded at all).""",
74 loaded at all).""",
73 metavar='Global.config_file')
75 metavar='Global.config_file')
74 paa('--autocall',
76 paa('--autocall',
75 dest='InteractiveShell.autocall', type=int,
77 dest='InteractiveShell.autocall', type=int,
76 help=
78 help=
77 """Make IPython automatically call any callable object even if you
79 """Make IPython automatically call any callable object even if you
78 didn't type explicit parentheses. For example, 'str 43' becomes
80 didn't type explicit parentheses. For example, 'str 43' becomes
79 'str(43)' automatically. The value can be '0' to disable the feature,
81 'str(43)' automatically. The value can be '0' to disable the feature,
80 '1' for 'smart' autocall, where it is not applied if there are no more
82 '1' for 'smart' autocall, where it is not applied if there are no more
81 arguments on the line, and '2' for 'full' autocall, where all callable
83 arguments on the line, and '2' for 'full' autocall, where all callable
82 objects are automatically called (even if no arguments are present).
84 objects are automatically called (even if no arguments are present).
83 The default is '1'.""",
85 The default is '1'.""",
84 metavar='InteractiveShell.autocall')
86 metavar='InteractiveShell.autocall')
85 paa('--autoindent',
87 paa('--autoindent',
86 action='store_true', dest='InteractiveShell.autoindent',
88 action='store_true', dest='InteractiveShell.autoindent',
87 help='Turn on autoindenting.')
89 help='Turn on autoindenting.')
88 paa('--no-autoindent',
90 paa('--no-autoindent',
89 action='store_false', dest='InteractiveShell.autoindent',
91 action='store_false', dest='InteractiveShell.autoindent',
90 help='Turn off autoindenting.')
92 help='Turn off autoindenting.')
91 paa('--automagic',
93 paa('--automagic',
92 action='store_true', dest='InteractiveShell.automagic',
94 action='store_true', dest='InteractiveShell.automagic',
93 help=
95 help=
94 """Turn on the auto calling of magic commands. Type %%magic at the
96 """Turn on the auto calling of magic commands. Type %%magic at the
95 IPython prompt for more information.""")
97 IPython prompt for more information.""")
96 paa('--no-automagic',
98 paa('--no-automagic',
97 action='store_false', dest='InteractiveShell.automagic',
99 action='store_false', dest='InteractiveShell.automagic',
98 help='Turn off the auto calling of magic commands.')
100 help='Turn off the auto calling of magic commands.')
99 paa('--autoedit-syntax',
101 paa('--autoedit-syntax',
100 action='store_true', dest='InteractiveShell.autoedit_syntax',
102 action='store_true', dest='InteractiveShell.autoedit_syntax',
101 help='Turn on auto editing of files with syntax errors.')
103 help='Turn on auto editing of files with syntax errors.')
102 paa('--no-autoedit-syntax',
104 paa('--no-autoedit-syntax',
103 action='store_false', dest='InteractiveShell.autoedit_syntax',
105 action='store_false', dest='InteractiveShell.autoedit_syntax',
104 help='Turn off auto editing of files with syntax errors.')
106 help='Turn off auto editing of files with syntax errors.')
105 paa('--banner',
107 paa('--banner',
106 action='store_true', dest='Global.display_banner',
108 action='store_true', dest='Global.display_banner',
107 help='Display a banner upon starting IPython.')
109 help='Display a banner upon starting IPython.')
108 paa('--no-banner',
110 paa('--no-banner',
109 action='store_false', dest='Global.display_banner',
111 action='store_false', dest='Global.display_banner',
110 help="Don't display a banner upon starting IPython.")
112 help="Don't display a banner upon starting IPython.")
111 paa('--cache-size',
113 paa('--cache-size',
112 type=int, dest='InteractiveShell.cache_size',
114 type=int, dest='InteractiveShell.cache_size',
113 help=
115 help=
114 """Set the size of the output cache. The default is 1000, you can
116 """Set the size of the output cache. The default is 1000, you can
115 change it permanently in your config file. Setting it to 0 completely
117 change it permanently in your config file. Setting it to 0 completely
116 disables the caching system, and the minimum value accepted is 20 (if
118 disables the caching system, and the minimum value accepted is 20 (if
117 you provide a value less than 20, it is reset to 0 and a warning is
119 you provide a value less than 20, it is reset to 0 and a warning is
118 issued). This limit is defined because otherwise you'll spend more
120 issued). This limit is defined because otherwise you'll spend more
119 time re-flushing a too small cache than working""",
121 time re-flushing a too small cache than working""",
120 metavar='InteractiveShell.cache_size')
122 metavar='InteractiveShell.cache_size')
121 paa('--classic',
123 paa('--classic',
122 action='store_true', dest='Global.classic',
124 action='store_true', dest='Global.classic',
123 help="Gives IPython a similar feel to the classic Python prompt.")
125 help="Gives IPython a similar feel to the classic Python prompt.")
124 paa('--colors',
126 paa('--colors',
125 type=str, dest='InteractiveShell.colors',
127 type=str, dest='InteractiveShell.colors',
126 help="Set the color scheme (NoColor, Linux, and LightBG).",
128 help="Set the color scheme (NoColor, Linux, and LightBG).",
127 metavar='InteractiveShell.colors')
129 metavar='InteractiveShell.colors')
128 paa('--color-info',
130 paa('--color-info',
129 action='store_true', dest='InteractiveShell.color_info',
131 action='store_true', dest='InteractiveShell.color_info',
130 help=
132 help=
131 """IPython can display information about objects via a set of func-
133 """IPython can display information about objects via a set of func-
132 tions, and optionally can use colors for this, syntax highlighting
134 tions, and optionally can use colors for this, syntax highlighting
133 source code and various other elements. However, because this
135 source code and various other elements. However, because this
134 information is passed through a pager (like 'less') and many pagers get
136 information is passed through a pager (like 'less') and many pagers get
135 confused with color codes, this option is off by default. You can test
137 confused with color codes, this option is off by default. You can test
136 it and turn it on permanently in your ipython_config.py file if it
138 it and turn it on permanently in your ipython_config.py file if it
137 works for you. Test it and turn it on permanently if it works with
139 works for you. Test it and turn it on permanently if it works with
138 your system. The magic function %%color_info allows you to toggle this
140 your system. The magic function %%color_info allows you to toggle this
139 inter- actively for testing.""")
141 inter- actively for testing.""")
140 paa('--no-color-info',
142 paa('--no-color-info',
141 action='store_false', dest='InteractiveShell.color_info',
143 action='store_false', dest='InteractiveShell.color_info',
142 help="Disable using colors for info related things.")
144 help="Disable using colors for info related things.")
143 paa('--confirm-exit',
145 paa('--confirm-exit',
144 action='store_true', dest='InteractiveShell.confirm_exit',
146 action='store_true', dest='InteractiveShell.confirm_exit',
145 help=
147 help=
146 """Set to confirm when you try to exit IPython with an EOF (Control-D
148 """Set to confirm when you try to exit IPython with an EOF (Control-D
147 in Unix, Control-Z/Enter in Windows). By typing 'exit', 'quit' or
149 in Unix, Control-Z/Enter in Windows). By typing 'exit', 'quit' or
148 '%%Exit', you can force a direct exit without any confirmation.""")
150 '%%Exit', you can force a direct exit without any confirmation.""")
149 paa('--no-confirm-exit',
151 paa('--no-confirm-exit',
150 action='store_false', dest='InteractiveShell.confirm_exit',
152 action='store_false', dest='InteractiveShell.confirm_exit',
151 help="Don't prompt the user when exiting.")
153 help="Don't prompt the user when exiting.")
152 paa('--deep-reload',
154 paa('--deep-reload',
153 action='store_true', dest='InteractiveShell.deep_reload',
155 action='store_true', dest='InteractiveShell.deep_reload',
154 help=
156 help=
155 """Enable deep (recursive) reloading by default. IPython can use the
157 """Enable deep (recursive) reloading by default. IPython can use the
156 deep_reload module which reloads changes in modules recursively (it
158 deep_reload module which reloads changes in modules recursively (it
157 replaces the reload() function, so you don't need to change anything to
159 replaces the reload() function, so you don't need to change anything to
158 use it). deep_reload() forces a full reload of modules whose code may
160 use it). deep_reload() forces a full reload of modules whose code may
159 have changed, which the default reload() function does not. When
161 have changed, which the default reload() function does not. When
160 deep_reload is off, IPython will use the normal reload(), but
162 deep_reload is off, IPython will use the normal reload(), but
161 deep_reload will still be available as dreload(). This fea- ture is off
163 deep_reload will still be available as dreload(). This fea- ture is off
162 by default [which means that you have both normal reload() and
164 by default [which means that you have both normal reload() and
163 dreload()].""")
165 dreload()].""")
164 paa('--no-deep-reload',
166 paa('--no-deep-reload',
165 action='store_false', dest='InteractiveShell.deep_reload',
167 action='store_false', dest='InteractiveShell.deep_reload',
166 help="Disable deep (recursive) reloading by default.")
168 help="Disable deep (recursive) reloading by default.")
167 paa('--editor',
169 paa('--editor',
168 type=str, dest='InteractiveShell.editor',
170 type=str, dest='InteractiveShell.editor',
169 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
171 help="Set the editor used by IPython (default to $EDITOR/vi/notepad).",
170 metavar='InteractiveShell.editor')
172 metavar='InteractiveShell.editor')
171 paa('--log','-l',
173 paa('--log','-l',
172 action='store_true', dest='InteractiveShell.logstart',
174 action='store_true', dest='InteractiveShell.logstart',
173 help="Start logging to the default log file (./ipython_log.py).")
175 help="Start logging to the default log file (./ipython_log.py).")
174 paa('--logfile','-lf',
176 paa('--logfile','-lf',
175 type=unicode, dest='InteractiveShell.logfile',
177 type=unicode, dest='InteractiveShell.logfile',
176 help="Start logging to logfile with this name.",
178 help="Start logging to logfile with this name.",
177 metavar='InteractiveShell.logfile')
179 metavar='InteractiveShell.logfile')
178 paa('--log-append','-la',
180 paa('--log-append','-la',
179 type=unicode, dest='InteractiveShell.logappend',
181 type=unicode, dest='InteractiveShell.logappend',
180 help="Start logging to the given file in append mode.",
182 help="Start logging to the given file in append mode.",
181 metavar='InteractiveShell.logfile')
183 metavar='InteractiveShell.logfile')
182 paa('--pdb',
184 paa('--pdb',
183 action='store_true', dest='InteractiveShell.pdb',
185 action='store_true', dest='InteractiveShell.pdb',
184 help="Enable auto calling the pdb debugger after every exception.")
186 help="Enable auto calling the pdb debugger after every exception.")
185 paa('--no-pdb',
187 paa('--no-pdb',
186 action='store_false', dest='InteractiveShell.pdb',
188 action='store_false', dest='InteractiveShell.pdb',
187 help="Disable auto calling the pdb debugger after every exception.")
189 help="Disable auto calling the pdb debugger after every exception.")
188 paa('--pprint',
190 paa('--pprint',
189 action='store_true', dest='InteractiveShell.pprint',
191 action='store_true', dest='InteractiveShell.pprint',
190 help="Enable auto pretty printing of results.")
192 help="Enable auto pretty printing of results.")
191 paa('--no-pprint',
193 paa('--no-pprint',
192 action='store_false', dest='InteractiveShell.pprint',
194 action='store_false', dest='InteractiveShell.pprint',
193 help="Disable auto auto pretty printing of results.")
195 help="Disable auto auto pretty printing of results.")
194 paa('--prompt-in1','-pi1',
196 paa('--prompt-in1','-pi1',
195 type=str, dest='InteractiveShell.prompt_in1',
197 type=str, dest='InteractiveShell.prompt_in1',
196 help=
198 help=
197 """Set the main input prompt ('In [\#]: '). Note that if you are using
199 """Set the main input prompt ('In [\#]: '). Note that if you are using
198 numbered prompts, the number is represented with a '\#' in the string.
200 numbered prompts, the number is represented with a '\#' in the string.
199 Don't forget to quote strings with spaces embedded in them. Most
201 Don't forget to quote strings with spaces embedded in them. Most
200 bash-like escapes can be used to customize IPython's prompts, as well
202 bash-like escapes can be used to customize IPython's prompts, as well
201 as a few additional ones which are IPython-spe- cific. All valid
203 as a few additional ones which are IPython-spe- cific. All valid
202 prompt escapes are described in detail in the Customization section of
204 prompt escapes are described in detail in the Customization section of
203 the IPython manual.""",
205 the IPython manual.""",
204 metavar='InteractiveShell.prompt_in1')
206 metavar='InteractiveShell.prompt_in1')
205 paa('--prompt-in2','-pi2',
207 paa('--prompt-in2','-pi2',
206 type=str, dest='InteractiveShell.prompt_in2',
208 type=str, dest='InteractiveShell.prompt_in2',
207 help=
209 help=
208 """Set the secondary input prompt (' .\D.: '). Similar to the previous
210 """Set the secondary input prompt (' .\D.: '). Similar to the previous
209 option, but used for the continuation prompts. The special sequence
211 option, but used for the continuation prompts. The special sequence
210 '\D' is similar to '\#', but with all digits replaced by dots (so you
212 '\D' is similar to '\#', but with all digits replaced by dots (so you
211 can have your continuation prompt aligned with your input prompt).
213 can have your continuation prompt aligned with your input prompt).
212 Default: ' .\D.: ' (note three spaces at the start for alignment with
214 Default: ' .\D.: ' (note three spaces at the start for alignment with
213 'In [\#]')""",
215 'In [\#]')""",
214 metavar='InteractiveShell.prompt_in2')
216 metavar='InteractiveShell.prompt_in2')
215 paa('--prompt-out','-po',
217 paa('--prompt-out','-po',
216 type=str, dest='InteractiveShell.prompt_out',
218 type=str, dest='InteractiveShell.prompt_out',
217 help="Set the output prompt ('Out[\#]:')",
219 help="Set the output prompt ('Out[\#]:')",
218 metavar='InteractiveShell.prompt_out')
220 metavar='InteractiveShell.prompt_out')
219 paa('--quick',
221 paa('--quick',
220 action='store_true', dest='Global.quick',
222 action='store_true', dest='Global.quick',
221 help="Enable quick startup with no config files.")
223 help="Enable quick startup with no config files.")
222 paa('--readline',
224 paa('--readline',
223 action='store_true', dest='InteractiveShell.readline_use',
225 action='store_true', dest='InteractiveShell.readline_use',
224 help="Enable readline for command line usage.")
226 help="Enable readline for command line usage.")
225 paa('--no-readline',
227 paa('--no-readline',
226 action='store_false', dest='InteractiveShell.readline_use',
228 action='store_false', dest='InteractiveShell.readline_use',
227 help="Disable readline for command line usage.")
229 help="Disable readline for command line usage.")
228 paa('--screen-length','-sl',
230 paa('--screen-length','-sl',
229 type=int, dest='InteractiveShell.screen_length',
231 type=int, dest='InteractiveShell.screen_length',
230 help=
232 help=
231 """Number of lines of your screen, used to control printing of very
233 """Number of lines of your screen, used to control printing of very
232 long strings. Strings longer than this number of lines will be sent
234 long strings. Strings longer than this number of lines will be sent
233 through a pager instead of directly printed. The default value for
235 through a pager instead of directly printed. The default value for
234 this is 0, which means IPython will auto-detect your screen size every
236 this is 0, which means IPython will auto-detect your screen size every
235 time it needs to print certain potentially long strings (this doesn't
237 time it needs to print certain potentially long strings (this doesn't
236 change the behavior of the 'print' keyword, it's only triggered
238 change the behavior of the 'print' keyword, it's only triggered
237 internally). If for some reason this isn't working well (it needs
239 internally). If for some reason this isn't working well (it needs
238 curses support), specify it yourself. Otherwise don't change the
240 curses support), specify it yourself. Otherwise don't change the
239 default.""",
241 default.""",
240 metavar='InteractiveShell.screen_length')
242 metavar='InteractiveShell.screen_length')
241 paa('--separate-in','-si',
243 paa('--separate-in','-si',
242 type=str, dest='InteractiveShell.separate_in',
244 type=str, dest='InteractiveShell.separate_in',
243 help="Separator before input prompts. Default '\\n'.",
245 help="Separator before input prompts. Default '\\n'.",
244 metavar='InteractiveShell.separate_in')
246 metavar='InteractiveShell.separate_in')
245 paa('--separate-out','-so',
247 paa('--separate-out','-so',
246 type=str, dest='InteractiveShell.separate_out',
248 type=str, dest='InteractiveShell.separate_out',
247 help="Separator before output prompts. Default 0 (nothing).",
249 help="Separator before output prompts. Default 0 (nothing).",
248 metavar='InteractiveShell.separate_out')
250 metavar='InteractiveShell.separate_out')
249 paa('--separate-out2','-so2',
251 paa('--separate-out2','-so2',
250 type=str, dest='InteractiveShell.separate_out2',
252 type=str, dest='InteractiveShell.separate_out2',
251 help="Separator after output prompts. Default 0 (nonight).",
253 help="Separator after output prompts. Default 0 (nonight).",
252 metavar='InteractiveShell.separate_out2')
254 metavar='InteractiveShell.separate_out2')
253 paa('-no-sep',
255 paa('-no-sep',
254 action='store_true', dest='Global.nosep',
256 action='store_true', dest='Global.nosep',
255 help="Eliminate all spacing between prompts.")
257 help="Eliminate all spacing between prompts.")
256 paa('--term-title',
258 paa('--term-title',
257 action='store_true', dest='InteractiveShell.term_title',
259 action='store_true', dest='InteractiveShell.term_title',
258 help="Enable auto setting the terminal title.")
260 help="Enable auto setting the terminal title.")
259 paa('--no-term-title',
261 paa('--no-term-title',
260 action='store_false', dest='InteractiveShell.term_title',
262 action='store_false', dest='InteractiveShell.term_title',
261 help="Disable auto setting the terminal title.")
263 help="Disable auto setting the terminal title.")
262 paa('--xmode',
264 paa('--xmode',
263 type=str, dest='InteractiveShell.xmode',
265 type=str, dest='InteractiveShell.xmode',
264 help=
266 help=
265 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
267 """Exception reporting mode ('Plain','Context','Verbose'). Plain:
266 similar to python's normal traceback printing. Context: prints 5 lines
268 similar to python's normal traceback printing. Context: prints 5 lines
267 of context source code around each line in the traceback. Verbose:
269 of context source code around each line in the traceback. Verbose:
268 similar to Context, but additionally prints the variables currently
270 similar to Context, but additionally prints the variables currently
269 visible where the exception happened (shortening their strings if too
271 visible where the exception happened (shortening their strings if too
270 long). This can potentially be very slow, if you happen to have a huge
272 long). This can potentially be very slow, if you happen to have a huge
271 data structure whose string representation is complex to compute.
273 data structure whose string representation is complex to compute.
272 Your computer may appear to freeze for a while with cpu usage at 100%%.
274 Your computer may appear to freeze for a while with cpu usage at 100%%.
273 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
275 If this occurs, you can cancel the traceback with Ctrl-C (maybe hitting
274 it more than once).
276 it more than once).
275 """,
277 """,
276 metavar='InteractiveShell.xmode')
278 metavar='InteractiveShell.xmode')
277 paa('--ext',
279 paa('--ext',
278 type=str, dest='Global.extra_extension',
280 type=str, dest='Global.extra_extension',
279 help="The dotted module name of an IPython extension to load.",
281 help="The dotted module name of an IPython extension to load.",
280 metavar='Global.extra_extension')
282 metavar='Global.extra_extension')
281 paa('-c',
283 paa('-c',
282 type=str, dest='Global.code_to_run',
284 type=str, dest='Global.code_to_run',
283 help="Execute the given command string.",
285 help="Execute the given command string.",
284 metavar='Global.code_to_run')
286 metavar='Global.code_to_run')
285 paa('-i',
287 paa('-i',
286 action='store_true', dest='Global.force_interact',
288 action='store_true', dest='Global.force_interact',
287 help=
289 help=
288 "If running code from the command line, become interactive afterwards.")
290 "If running code from the command line, become interactive afterwards.")
289
291
290 # Options to start with GUI control enabled from the beginning
292 # Options to start with GUI control enabled from the beginning
291 paa('--gui',
293 paa('--gui',
292 type=str, dest='Global.gui',
294 type=str, dest='Global.gui',
293 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
295 help="Enable GUI event loop integration ('qt', 'wx', 'gtk').",
294 metavar='gui-mode')
296 metavar='gui-mode')
295 paa('--pylab','-pylab',
297 paa('--pylab','-pylab',
296 type=str, dest='Global.pylab',
298 type=str, dest='Global.pylab',
297 nargs='?', const='auto', metavar='gui-mode',
299 nargs='?', const='auto', metavar='gui-mode',
298 help="Pre-load matplotlib and numpy for interactive use. "+
300 help="Pre-load matplotlib and numpy for interactive use. "+
299 "If no value is given, the gui backend is matplotlib's, else use "+
301 "If no value is given, the gui backend is matplotlib's, else use "+
300 "one of: ['tk', 'qt', 'wx', 'gtk'].")
302 "one of: ['tk', 'qt', 'wx', 'gtk'].")
301
303
302 # Legacy GUI options. Leave them in for backwards compatibility, but the
304 # Legacy GUI options. Leave them in for backwards compatibility, but the
303 # 'thread' names are really a misnomer now.
305 # 'thread' names are really a misnomer now.
304 paa('--wthread', '-wthread',
306 paa('--wthread', '-wthread',
305 action='store_true', dest='Global.wthread',
307 action='store_true', dest='Global.wthread',
306 help=
308 help=
307 """Enable wxPython event loop integration. (DEPRECATED, use --gui wx)""")
309 """Enable wxPython event loop integration. (DEPRECATED, use --gui wx)""")
308 paa('--q4thread', '--qthread', '-q4thread', '-qthread',
310 paa('--q4thread', '--qthread', '-q4thread', '-qthread',
309 action='store_true', dest='Global.q4thread',
311 action='store_true', dest='Global.q4thread',
310 help=
312 help=
311 """Enable Qt4 event loop integration. Qt3 is no longer supported.
313 """Enable Qt4 event loop integration. Qt3 is no longer supported.
312 (DEPRECATED, use --gui qt)""")
314 (DEPRECATED, use --gui qt)""")
313 paa('--gthread', '-gthread',
315 paa('--gthread', '-gthread',
314 action='store_true', dest='Global.gthread',
316 action='store_true', dest='Global.gthread',
315 help=
317 help=
316 """Enable GTK event loop integration. (DEPRECATED, use --gui gtk)""")
318 """Enable GTK event loop integration. (DEPRECATED, use --gui gtk)""")
317
319
318
320
319 #-----------------------------------------------------------------------------
321 #-----------------------------------------------------------------------------
322 # Crash handler for this application
323 #-----------------------------------------------------------------------------
324
325
326 _message_template = """\
327 Oops, $self.app_name crashed. We do our best to make it stable, but...
328
329 A crash report was automatically generated with the following information:
330 - A verbatim copy of the crash traceback.
331 - A copy of your input history during this session.
332 - Data on your current $self.app_name configuration.
333
334 It was left in the file named:
335 \t'$self.crash_report_fname'
336 If you can email this file to the developers, the information in it will help
337 them in understanding and correcting the problem.
338
339 You can mail it to: $self.contact_name at $self.contact_email
340 with the subject '$self.app_name Crash Report'.
341
342 If you want to do it now, the following command will work (under Unix):
343 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
344
345 To ensure accurate tracking of this issue, please file a report about it at:
346 $self.bug_tracker
347 """
348
349 class IPAppCrashHandler(CrashHandler):
350 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
351
352 message_template = _message_template
353
354 def __init__(self, app):
355 contact_name = release.authors['Fernando'][0]
356 contact_email = release.authors['Fernando'][1]
357 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
358 super(IPAppCrashHandler,self).__init__(
359 app, contact_name, contact_email, bug_tracker
360 )
361
362 def make_report(self,traceback):
363 """Return a string containing a crash report."""
364
365 sec_sep = self.section_sep
366 # Start with parent report
367 report = [super(IPAppCrashHandler, self).make_report(traceback)]
368 # Add interactive-specific info we may have
369 rpt_add = report.append
370 try:
371 rpt_add(sec_sep+"History of session input:")
372 for line in self.app.shell.user_ns['_ih']:
373 rpt_add(line)
374 rpt_add('\n*** Last line of input (may not be in above history):\n')
375 rpt_add(self.app.shell._last_input_line+'\n')
376 except:
377 pass
378
379 return ''.join(report)
380
381
382 #-----------------------------------------------------------------------------
320 # Main classes and functions
383 # Main classes and functions
321 #-----------------------------------------------------------------------------
384 #-----------------------------------------------------------------------------
322
385
323 class IPythonApp(Application):
386 class IPythonApp(Application):
324 name = u'ipython'
387 name = u'ipython'
325 #: argparse formats better the 'usage' than the 'description' field
388 #: argparse formats better the 'usage' than the 'description' field
326 description = None
389 description = None
327 usage = usage.cl_usage
390 usage = usage.cl_usage
328 command_line_loader = IPAppConfigLoader
391 command_line_loader = IPAppConfigLoader
329 config_file_name = default_config_file_name
392 config_file_name = default_config_file_name
330
393 crash_handler_class = IPAppCrashHandler
331 # Private and configuration attributes
332 _CrashHandler = crashhandler.IPythonCrashHandler
333
394
334 def create_default_config(self):
395 def create_default_config(self):
335 super(IPythonApp, self).create_default_config()
396 super(IPythonApp, self).create_default_config()
336 # Eliminate multiple lookups
397 # Eliminate multiple lookups
337 Global = self.default_config.Global
398 Global = self.default_config.Global
338
399
339 # Set all default values
400 # Set all default values
340 Global.display_banner = True
401 Global.display_banner = True
341
402
342 # If the -c flag is given or a file is given to run at the cmd line
403 # If the -c flag is given or a file is given to run at the cmd line
343 # like "ipython foo.py", normally we exit without starting the main
404 # like "ipython foo.py", normally we exit without starting the main
344 # loop. The force_interact config variable allows a user to override
405 # loop. The force_interact config variable allows a user to override
345 # this and interact. It is also set by the -i cmd line flag, just
406 # this and interact. It is also set by the -i cmd line flag, just
346 # like Python.
407 # like Python.
347 Global.force_interact = False
408 Global.force_interact = False
348
409
349 # By default always interact by starting the IPython mainloop.
410 # By default always interact by starting the IPython mainloop.
350 Global.interact = True
411 Global.interact = True
351
412
352 # No GUI integration by default
413 # No GUI integration by default
353 Global.gui = False
414 Global.gui = False
354 # Pylab off by default
415 # Pylab off by default
355 Global.pylab = False
416 Global.pylab = False
356
417
357 # Deprecated versions of gui support that used threading, we support
418 # Deprecated versions of gui support that used threading, we support
358 # them just for bacwards compatibility as an alternate spelling for
419 # them just for bacwards compatibility as an alternate spelling for
359 # '--gui X'
420 # '--gui X'
360 Global.qthread = False
421 Global.qthread = False
361 Global.q4thread = False
422 Global.q4thread = False
362 Global.wthread = False
423 Global.wthread = False
363 Global.gthread = False
424 Global.gthread = False
364
425
365 def load_file_config(self):
426 def load_file_config(self):
366 if hasattr(self.command_line_config.Global, 'quick'):
427 if hasattr(self.command_line_config.Global, 'quick'):
367 if self.command_line_config.Global.quick:
428 if self.command_line_config.Global.quick:
368 self.file_config = Config()
429 self.file_config = Config()
369 return
430 return
370 super(IPythonApp, self).load_file_config()
431 super(IPythonApp, self).load_file_config()
371
432
372 def post_load_file_config(self):
433 def post_load_file_config(self):
373 if hasattr(self.command_line_config.Global, 'extra_extension'):
434 if hasattr(self.command_line_config.Global, 'extra_extension'):
374 if not hasattr(self.file_config.Global, 'extensions'):
435 if not hasattr(self.file_config.Global, 'extensions'):
375 self.file_config.Global.extensions = []
436 self.file_config.Global.extensions = []
376 self.file_config.Global.extensions.append(
437 self.file_config.Global.extensions.append(
377 self.command_line_config.Global.extra_extension)
438 self.command_line_config.Global.extra_extension)
378 del self.command_line_config.Global.extra_extension
439 del self.command_line_config.Global.extra_extension
379
440
380 def pre_construct(self):
441 def pre_construct(self):
381 config = self.master_config
442 config = self.master_config
382
443
383 if hasattr(config.Global, 'classic'):
444 if hasattr(config.Global, 'classic'):
384 if config.Global.classic:
445 if config.Global.classic:
385 config.InteractiveShell.cache_size = 0
446 config.InteractiveShell.cache_size = 0
386 config.InteractiveShell.pprint = 0
447 config.InteractiveShell.pprint = 0
387 config.InteractiveShell.prompt_in1 = '>>> '
448 config.InteractiveShell.prompt_in1 = '>>> '
388 config.InteractiveShell.prompt_in2 = '... '
449 config.InteractiveShell.prompt_in2 = '... '
389 config.InteractiveShell.prompt_out = ''
450 config.InteractiveShell.prompt_out = ''
390 config.InteractiveShell.separate_in = \
451 config.InteractiveShell.separate_in = \
391 config.InteractiveShell.separate_out = \
452 config.InteractiveShell.separate_out = \
392 config.InteractiveShell.separate_out2 = ''
453 config.InteractiveShell.separate_out2 = ''
393 config.InteractiveShell.colors = 'NoColor'
454 config.InteractiveShell.colors = 'NoColor'
394 config.InteractiveShell.xmode = 'Plain'
455 config.InteractiveShell.xmode = 'Plain'
395
456
396 if hasattr(config.Global, 'nosep'):
457 if hasattr(config.Global, 'nosep'):
397 if config.Global.nosep:
458 if config.Global.nosep:
398 config.InteractiveShell.separate_in = \
459 config.InteractiveShell.separate_in = \
399 config.InteractiveShell.separate_out = \
460 config.InteractiveShell.separate_out = \
400 config.InteractiveShell.separate_out2 = ''
461 config.InteractiveShell.separate_out2 = ''
401
462
402 # if there is code of files to run from the cmd line, don't interact
463 # if there is code of files to run from the cmd line, don't interact
403 # unless the -i flag (Global.force_interact) is true.
464 # unless the -i flag (Global.force_interact) is true.
404 code_to_run = config.Global.get('code_to_run','')
465 code_to_run = config.Global.get('code_to_run','')
405 file_to_run = False
466 file_to_run = False
406 if self.extra_args and self.extra_args[0]:
467 if self.extra_args and self.extra_args[0]:
407 file_to_run = True
468 file_to_run = True
408 if file_to_run or code_to_run:
469 if file_to_run or code_to_run:
409 if not config.Global.force_interact:
470 if not config.Global.force_interact:
410 config.Global.interact = False
471 config.Global.interact = False
411
472
412 def construct(self):
473 def construct(self):
413 # I am a little hesitant to put these into InteractiveShell itself.
474 # I am a little hesitant to put these into InteractiveShell itself.
414 # But that might be the place for them
475 # But that might be the place for them
415 sys.path.insert(0, '')
476 sys.path.insert(0, '')
416
477
417 # Create an InteractiveShell instance
478 # Create an InteractiveShell instance
418 self.shell = InteractiveShell(None, self.master_config)
479 self.shell = InteractiveShell(None, self.master_config)
419
480
420 def post_construct(self):
481 def post_construct(self):
421 """Do actions after construct, but before starting the app."""
482 """Do actions after construct, but before starting the app."""
422 config = self.master_config
483 config = self.master_config
423
484
424 # shell.display_banner should always be False for the terminal
485 # shell.display_banner should always be False for the terminal
425 # based app, because we call shell.show_banner() by hand below
486 # based app, because we call shell.show_banner() by hand below
426 # so the banner shows *before* all extension loading stuff.
487 # so the banner shows *before* all extension loading stuff.
427 self.shell.display_banner = False
488 self.shell.display_banner = False
428
489
429 if config.Global.display_banner and \
490 if config.Global.display_banner and \
430 config.Global.interact:
491 config.Global.interact:
431 self.shell.show_banner()
492 self.shell.show_banner()
432
493
433 # Make sure there is a space below the banner.
494 # Make sure there is a space below the banner.
434 if self.log_level <= logging.INFO: print
495 if self.log_level <= logging.INFO: print
435
496
436 # Now a variety of things that happen after the banner is printed.
497 # Now a variety of things that happen after the banner is printed.
437 self._enable_gui_pylab()
498 self._enable_gui_pylab()
438 self._load_extensions()
499 self._load_extensions()
439 self._run_exec_lines()
500 self._run_exec_lines()
440 self._run_exec_files()
501 self._run_exec_files()
441 self._run_cmd_line_code()
502 self._run_cmd_line_code()
442
503
443 def _enable_gui_pylab(self):
504 def _enable_gui_pylab(self):
444 """Enable GUI event loop integration, taking pylab into account."""
505 """Enable GUI event loop integration, taking pylab into account."""
445 Global = self.master_config.Global
506 Global = self.master_config.Global
446
507
447 # Select which gui to use
508 # Select which gui to use
448 if Global.gui:
509 if Global.gui:
449 gui = Global.gui
510 gui = Global.gui
450 # The following are deprecated, but there's likely to be a lot of use
511 # The following are deprecated, but there's likely to be a lot of use
451 # of this form out there, so we might as well support it for now. But
512 # of this form out there, so we might as well support it for now. But
452 # the --gui option above takes precedence.
513 # the --gui option above takes precedence.
453 elif Global.wthread:
514 elif Global.wthread:
454 gui = inputhook.GUI_WX
515 gui = inputhook.GUI_WX
455 elif Global.qthread:
516 elif Global.qthread:
456 gui = inputhook.GUI_QT
517 gui = inputhook.GUI_QT
457 elif Global.gthread:
518 elif Global.gthread:
458 gui = inputhook.GUI_GTK
519 gui = inputhook.GUI_GTK
459 else:
520 else:
460 gui = None
521 gui = None
461
522
462 # Using --pylab will also require gui activation, though which toolkit
523 # Using --pylab will also require gui activation, though which toolkit
463 # to use may be chosen automatically based on mpl configuration.
524 # to use may be chosen automatically based on mpl configuration.
464 if Global.pylab:
525 if Global.pylab:
465 activate = self.shell.enable_pylab
526 activate = self.shell.enable_pylab
466 if Global.pylab == 'auto':
527 if Global.pylab == 'auto':
467 gui = None
528 gui = None
468 else:
529 else:
469 gui = Global.pylab
530 gui = Global.pylab
470 else:
531 else:
471 # Enable only GUI integration, no pylab
532 # Enable only GUI integration, no pylab
472 activate = inputhook.enable_gui
533 activate = inputhook.enable_gui
473
534
474 if gui or Global.pylab:
535 if gui or Global.pylab:
475 try:
536 try:
476 self.log.info("Enabling GUI event loop integration, "
537 self.log.info("Enabling GUI event loop integration, "
477 "toolkit=%s, pylab=%s" % (gui, Global.pylab) )
538 "toolkit=%s, pylab=%s" % (gui, Global.pylab) )
478 activate(gui)
539 activate(gui)
479 except:
540 except:
480 self.log.warn("Error in enabling GUI event loop integration:")
541 self.log.warn("Error in enabling GUI event loop integration:")
481 self.shell.showtraceback()
542 self.shell.showtraceback()
482
543
483 def _load_extensions(self):
544 def _load_extensions(self):
484 """Load all IPython extensions in Global.extensions.
545 """Load all IPython extensions in Global.extensions.
485
546
486 This uses the :meth:`InteractiveShell.load_extensions` to load all
547 This uses the :meth:`InteractiveShell.load_extensions` to load all
487 the extensions listed in ``self.master_config.Global.extensions``.
548 the extensions listed in ``self.master_config.Global.extensions``.
488 """
549 """
489 try:
550 try:
490 if hasattr(self.master_config.Global, 'extensions'):
551 if hasattr(self.master_config.Global, 'extensions'):
491 self.log.debug("Loading IPython extensions...")
552 self.log.debug("Loading IPython extensions...")
492 extensions = self.master_config.Global.extensions
553 extensions = self.master_config.Global.extensions
493 for ext in extensions:
554 for ext in extensions:
494 try:
555 try:
495 self.log.info("Loading IPython extension: %s" % ext)
556 self.log.info("Loading IPython extension: %s" % ext)
496 self.shell.load_extension(ext)
557 self.shell.load_extension(ext)
497 except:
558 except:
498 self.log.warn("Error in loading extension: %s" % ext)
559 self.log.warn("Error in loading extension: %s" % ext)
499 self.shell.showtraceback()
560 self.shell.showtraceback()
500 except:
561 except:
501 self.log.warn("Unknown error in loading extensions:")
562 self.log.warn("Unknown error in loading extensions:")
502 self.shell.showtraceback()
563 self.shell.showtraceback()
503
564
504 def _run_exec_lines(self):
565 def _run_exec_lines(self):
505 """Run lines of code in Global.exec_lines in the user's namespace."""
566 """Run lines of code in Global.exec_lines in the user's namespace."""
506 try:
567 try:
507 if hasattr(self.master_config.Global, 'exec_lines'):
568 if hasattr(self.master_config.Global, 'exec_lines'):
508 self.log.debug("Running code from Global.exec_lines...")
569 self.log.debug("Running code from Global.exec_lines...")
509 exec_lines = self.master_config.Global.exec_lines
570 exec_lines = self.master_config.Global.exec_lines
510 for line in exec_lines:
571 for line in exec_lines:
511 try:
572 try:
512 self.log.info("Running code in user namespace: %s" % line)
573 self.log.info("Running code in user namespace: %s" % line)
513 self.shell.runlines(line)
574 self.shell.runlines(line)
514 except:
575 except:
515 self.log.warn("Error in executing line in user namespace: %s" % line)
576 self.log.warn("Error in executing line in user namespace: %s" % line)
516 self.shell.showtraceback()
577 self.shell.showtraceback()
517 except:
578 except:
518 self.log.warn("Unknown error in handling Global.exec_lines:")
579 self.log.warn("Unknown error in handling Global.exec_lines:")
519 self.shell.showtraceback()
580 self.shell.showtraceback()
520
581
521 def _exec_file(self, fname):
582 def _exec_file(self, fname):
522 full_filename = filefind(fname, [u'.', self.ipython_dir])
583 full_filename = filefind(fname, [u'.', self.ipython_dir])
523 if os.path.isfile(full_filename):
584 if os.path.isfile(full_filename):
524 if full_filename.endswith(u'.py'):
585 if full_filename.endswith(u'.py'):
525 self.log.info("Running file in user namespace: %s" % full_filename)
586 self.log.info("Running file in user namespace: %s" % full_filename)
526 self.shell.safe_execfile(full_filename, self.shell.user_ns)
587 self.shell.safe_execfile(full_filename, self.shell.user_ns)
527 elif full_filename.endswith('.ipy'):
588 elif full_filename.endswith('.ipy'):
528 self.log.info("Running file in user namespace: %s" % full_filename)
589 self.log.info("Running file in user namespace: %s" % full_filename)
529 self.shell.safe_execfile_ipy(full_filename)
590 self.shell.safe_execfile_ipy(full_filename)
530 else:
591 else:
531 self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename)
592 self.log.warn("File does not have a .py or .ipy extension: <%s>" % full_filename)
532
593
533 def _run_exec_files(self):
594 def _run_exec_files(self):
534 try:
595 try:
535 if hasattr(self.master_config.Global, 'exec_files'):
596 if hasattr(self.master_config.Global, 'exec_files'):
536 self.log.debug("Running files in Global.exec_files...")
597 self.log.debug("Running files in Global.exec_files...")
537 exec_files = self.master_config.Global.exec_files
598 exec_files = self.master_config.Global.exec_files
538 for fname in exec_files:
599 for fname in exec_files:
539 self._exec_file(fname)
600 self._exec_file(fname)
540 except:
601 except:
541 self.log.warn("Unknown error in handling Global.exec_files:")
602 self.log.warn("Unknown error in handling Global.exec_files:")
542 self.shell.showtraceback()
603 self.shell.showtraceback()
543
604
544 def _run_cmd_line_code(self):
605 def _run_cmd_line_code(self):
545 if hasattr(self.master_config.Global, 'code_to_run'):
606 if hasattr(self.master_config.Global, 'code_to_run'):
546 line = self.master_config.Global.code_to_run
607 line = self.master_config.Global.code_to_run
547 try:
608 try:
548 self.log.info("Running code given at command line (-c): %s" % line)
609 self.log.info("Running code given at command line (-c): %s" % line)
549 self.shell.runlines(line)
610 self.shell.runlines(line)
550 except:
611 except:
551 self.log.warn("Error in executing line in user namespace: %s" % line)
612 self.log.warn("Error in executing line in user namespace: %s" % line)
552 self.shell.showtraceback()
613 self.shell.showtraceback()
553 return
614 return
554 # Like Python itself, ignore the second if the first of these is present
615 # Like Python itself, ignore the second if the first of these is present
555 try:
616 try:
556 fname = self.extra_args[0]
617 fname = self.extra_args[0]
557 except:
618 except:
558 pass
619 pass
559 else:
620 else:
560 try:
621 try:
561 self._exec_file(fname)
622 self._exec_file(fname)
562 except:
623 except:
563 self.log.warn("Error in executing file in user namespace: %s" % fname)
624 self.log.warn("Error in executing file in user namespace: %s" % fname)
564 self.shell.showtraceback()
625 self.shell.showtraceback()
565
626
566 def start_app(self):
627 def start_app(self):
567 if self.master_config.Global.interact:
628 if self.master_config.Global.interact:
568 self.log.debug("Starting IPython's mainloop...")
629 self.log.debug("Starting IPython's mainloop...")
569 self.shell.mainloop()
630 self.shell.mainloop()
570 else:
631 else:
571 self.log.debug("IPython not interactive, start_app is no-op...")
632 self.log.debug("IPython not interactive, start_app is no-op...")
572
633
573
634
574 def load_default_config(ipython_dir=None):
635 def load_default_config(ipython_dir=None):
575 """Load the default config file from the default ipython_dir.
636 """Load the default config file from the default ipython_dir.
576
637
577 This is useful for embedded shells.
638 This is useful for embedded shells.
578 """
639 """
579 if ipython_dir is None:
640 if ipython_dir is None:
580 ipython_dir = get_ipython_dir()
641 ipython_dir = get_ipython_dir()
581 cl = PyFileConfigLoader(default_config_file_name, ipython_dir)
642 cl = PyFileConfigLoader(default_config_file_name, ipython_dir)
582 config = cl.load_config()
643 config = cl.load_config()
583 return config
644 return config
584
645
585
646
586 def launch_new_instance():
647 def launch_new_instance():
587 """Create and run a full blown IPython instance"""
648 """Create and run a full blown IPython instance"""
588 app = IPythonApp()
649 app = IPythonApp()
589 app.start()
650 app.start()
590
651
@@ -1,493 +1,537 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The IPython cluster directory
4 The IPython cluster directory
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import with_statement
18 from __future__ import with_statement
19
19
20 import os
20 import os
21 import shutil
21 import shutil
22 import sys
22 import sys
23 import warnings
23 import warnings
24
24
25 from twisted.python import log
25 from twisted.python import log
26
26
27 from IPython.config.loader import PyFileConfigLoader
27 from IPython.config.loader import PyFileConfigLoader
28 from IPython.core.application import Application, BaseAppConfigLoader
28 from IPython.core.application import Application, BaseAppConfigLoader
29 from IPython.core.component import Component
29 from IPython.core.component import Component
30 from IPython.core.crashhandler import CrashHandler
31 from IPython.core import release
30 from IPython.utils.path import (
32 from IPython.utils.path import (
31 get_ipython_package_dir,
33 get_ipython_package_dir,
32 expand_path
34 expand_path
33 )
35 )
34 from IPython.utils.traitlets import Unicode
36 from IPython.utils.traitlets import Unicode
35
37
36 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
37 # Warnings control
39 # Warnings control
38 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
39 # Twisted generates annoying warnings with Python 2.6, as will do other code
41 # Twisted generates annoying warnings with Python 2.6, as will do other code
40 # that imports 'sets' as of today
42 # that imports 'sets' as of today
41 warnings.filterwarnings('ignore', 'the sets module is deprecated',
43 warnings.filterwarnings('ignore', 'the sets module is deprecated',
42 DeprecationWarning )
44 DeprecationWarning )
43
45
44 # This one also comes from Twisted
46 # This one also comes from Twisted
45 warnings.filterwarnings('ignore', 'the sha module is deprecated',
47 warnings.filterwarnings('ignore', 'the sha module is deprecated',
46 DeprecationWarning)
48 DeprecationWarning)
47
49
48 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
49 # Module errors
51 # Module errors
50 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
51
53
52 class ClusterDirError(Exception):
54 class ClusterDirError(Exception):
53 pass
55 pass
54
56
55
57
56 class PIDFileError(Exception):
58 class PIDFileError(Exception):
57 pass
59 pass
58
60
59
61
60 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
61 # Class for managing cluster directories
63 # Class for managing cluster directories
62 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
63
65
64 class ClusterDir(Component):
66 class ClusterDir(Component):
65 """An object to manage the cluster directory and its resources.
67 """An object to manage the cluster directory and its resources.
66
68
67 The cluster directory is used by :command:`ipcontroller`,
69 The cluster directory is used by :command:`ipcontroller`,
68 :command:`ipcontroller` and :command:`ipcontroller` to manage the
70 :command:`ipcontroller` and :command:`ipcontroller` to manage the
69 configuration, logging and security of these applications.
71 configuration, logging and security of these applications.
70
72
71 This object knows how to find, create and manage these directories. This
73 This object knows how to find, create and manage these directories. This
72 should be used by any code that want's to handle cluster directories.
74 should be used by any code that want's to handle cluster directories.
73 """
75 """
74
76
75 security_dir_name = Unicode('security')
77 security_dir_name = Unicode('security')
76 log_dir_name = Unicode('log')
78 log_dir_name = Unicode('log')
77 pid_dir_name = Unicode('pid')
79 pid_dir_name = Unicode('pid')
78 security_dir = Unicode(u'')
80 security_dir = Unicode(u'')
79 log_dir = Unicode(u'')
81 log_dir = Unicode(u'')
80 pid_dir = Unicode(u'')
82 pid_dir = Unicode(u'')
81 location = Unicode(u'')
83 location = Unicode(u'')
82
84
83 def __init__(self, location):
85 def __init__(self, location):
84 super(ClusterDir, self).__init__(None)
86 super(ClusterDir, self).__init__(None)
85 self.location = location
87 self.location = location
86
88
87 def _location_changed(self, name, old, new):
89 def _location_changed(self, name, old, new):
88 if not os.path.isdir(new):
90 if not os.path.isdir(new):
89 os.makedirs(new)
91 os.makedirs(new)
90 self.security_dir = os.path.join(new, self.security_dir_name)
92 self.security_dir = os.path.join(new, self.security_dir_name)
91 self.log_dir = os.path.join(new, self.log_dir_name)
93 self.log_dir = os.path.join(new, self.log_dir_name)
92 self.pid_dir = os.path.join(new, self.pid_dir_name)
94 self.pid_dir = os.path.join(new, self.pid_dir_name)
93 self.check_dirs()
95 self.check_dirs()
94
96
95 def _log_dir_changed(self, name, old, new):
97 def _log_dir_changed(self, name, old, new):
96 self.check_log_dir()
98 self.check_log_dir()
97
99
98 def check_log_dir(self):
100 def check_log_dir(self):
99 if not os.path.isdir(self.log_dir):
101 if not os.path.isdir(self.log_dir):
100 os.mkdir(self.log_dir)
102 os.mkdir(self.log_dir)
101
103
102 def _security_dir_changed(self, name, old, new):
104 def _security_dir_changed(self, name, old, new):
103 self.check_security_dir()
105 self.check_security_dir()
104
106
105 def check_security_dir(self):
107 def check_security_dir(self):
106 if not os.path.isdir(self.security_dir):
108 if not os.path.isdir(self.security_dir):
107 os.mkdir(self.security_dir, 0700)
109 os.mkdir(self.security_dir, 0700)
108 os.chmod(self.security_dir, 0700)
110 os.chmod(self.security_dir, 0700)
109
111
110 def _pid_dir_changed(self, name, old, new):
112 def _pid_dir_changed(self, name, old, new):
111 self.check_pid_dir()
113 self.check_pid_dir()
112
114
113 def check_pid_dir(self):
115 def check_pid_dir(self):
114 if not os.path.isdir(self.pid_dir):
116 if not os.path.isdir(self.pid_dir):
115 os.mkdir(self.pid_dir, 0700)
117 os.mkdir(self.pid_dir, 0700)
116 os.chmod(self.pid_dir, 0700)
118 os.chmod(self.pid_dir, 0700)
117
119
118 def check_dirs(self):
120 def check_dirs(self):
119 self.check_security_dir()
121 self.check_security_dir()
120 self.check_log_dir()
122 self.check_log_dir()
121 self.check_pid_dir()
123 self.check_pid_dir()
122
124
123 def load_config_file(self, filename):
125 def load_config_file(self, filename):
124 """Load a config file from the top level of the cluster dir.
126 """Load a config file from the top level of the cluster dir.
125
127
126 Parameters
128 Parameters
127 ----------
129 ----------
128 filename : unicode or str
130 filename : unicode or str
129 The filename only of the config file that must be located in
131 The filename only of the config file that must be located in
130 the top-level of the cluster directory.
132 the top-level of the cluster directory.
131 """
133 """
132 loader = PyFileConfigLoader(filename, self.location)
134 loader = PyFileConfigLoader(filename, self.location)
133 return loader.load_config()
135 return loader.load_config()
134
136
135 def copy_config_file(self, config_file, path=None, overwrite=False):
137 def copy_config_file(self, config_file, path=None, overwrite=False):
136 """Copy a default config file into the active cluster directory.
138 """Copy a default config file into the active cluster directory.
137
139
138 Default configuration files are kept in :mod:`IPython.config.default`.
140 Default configuration files are kept in :mod:`IPython.config.default`.
139 This function moves these from that location to the working cluster
141 This function moves these from that location to the working cluster
140 directory.
142 directory.
141 """
143 """
142 if path is None:
144 if path is None:
143 import IPython.config.default
145 import IPython.config.default
144 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
146 path = IPython.config.default.__file__.split(os.path.sep)[:-1]
145 path = os.path.sep.join(path)
147 path = os.path.sep.join(path)
146 src = os.path.join(path, config_file)
148 src = os.path.join(path, config_file)
147 dst = os.path.join(self.location, config_file)
149 dst = os.path.join(self.location, config_file)
148 if not os.path.isfile(dst) or overwrite:
150 if not os.path.isfile(dst) or overwrite:
149 shutil.copy(src, dst)
151 shutil.copy(src, dst)
150
152
151 def copy_all_config_files(self, path=None, overwrite=False):
153 def copy_all_config_files(self, path=None, overwrite=False):
152 """Copy all config files into the active cluster directory."""
154 """Copy all config files into the active cluster directory."""
153 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
155 for f in [u'ipcontroller_config.py', u'ipengine_config.py',
154 u'ipcluster_config.py']:
156 u'ipcluster_config.py']:
155 self.copy_config_file(f, path=path, overwrite=overwrite)
157 self.copy_config_file(f, path=path, overwrite=overwrite)
156
158
157 @classmethod
159 @classmethod
158 def create_cluster_dir(csl, cluster_dir):
160 def create_cluster_dir(csl, cluster_dir):
159 """Create a new cluster directory given a full path.
161 """Create a new cluster directory given a full path.
160
162
161 Parameters
163 Parameters
162 ----------
164 ----------
163 cluster_dir : str
165 cluster_dir : str
164 The full path to the cluster directory. If it does exist, it will
166 The full path to the cluster directory. If it does exist, it will
165 be used. If not, it will be created.
167 be used. If not, it will be created.
166 """
168 """
167 return ClusterDir(cluster_dir)
169 return ClusterDir(cluster_dir)
168
170
169 @classmethod
171 @classmethod
170 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
172 def create_cluster_dir_by_profile(cls, path, profile=u'default'):
171 """Create a cluster dir by profile name and path.
173 """Create a cluster dir by profile name and path.
172
174
173 Parameters
175 Parameters
174 ----------
176 ----------
175 path : str
177 path : str
176 The path (directory) to put the cluster directory in.
178 The path (directory) to put the cluster directory in.
177 profile : str
179 profile : str
178 The name of the profile. The name of the cluster directory will
180 The name of the profile. The name of the cluster directory will
179 be "cluster_<profile>".
181 be "cluster_<profile>".
180 """
182 """
181 if not os.path.isdir(path):
183 if not os.path.isdir(path):
182 raise ClusterDirError('Directory not found: %s' % path)
184 raise ClusterDirError('Directory not found: %s' % path)
183 cluster_dir = os.path.join(path, u'cluster_' + profile)
185 cluster_dir = os.path.join(path, u'cluster_' + profile)
184 return ClusterDir(cluster_dir)
186 return ClusterDir(cluster_dir)
185
187
186 @classmethod
188 @classmethod
187 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
189 def find_cluster_dir_by_profile(cls, ipython_dir, profile=u'default'):
188 """Find an existing cluster dir by profile name, return its ClusterDir.
190 """Find an existing cluster dir by profile name, return its ClusterDir.
189
191
190 This searches through a sequence of paths for a cluster dir. If it
192 This searches through a sequence of paths for a cluster dir. If it
191 is not found, a :class:`ClusterDirError` exception will be raised.
193 is not found, a :class:`ClusterDirError` exception will be raised.
192
194
193 The search path algorithm is:
195 The search path algorithm is:
194 1. ``os.getcwd()``
196 1. ``os.getcwd()``
195 2. ``ipython_dir``
197 2. ``ipython_dir``
196 3. The directories found in the ":" separated
198 3. The directories found in the ":" separated
197 :env:`IPCLUSTER_DIR_PATH` environment variable.
199 :env:`IPCLUSTER_DIR_PATH` environment variable.
198
200
199 Parameters
201 Parameters
200 ----------
202 ----------
201 ipython_dir : unicode or str
203 ipython_dir : unicode or str
202 The IPython directory to use.
204 The IPython directory to use.
203 profile : unicode or str
205 profile : unicode or str
204 The name of the profile. The name of the cluster directory
206 The name of the profile. The name of the cluster directory
205 will be "cluster_<profile>".
207 will be "cluster_<profile>".
206 """
208 """
207 dirname = u'cluster_' + profile
209 dirname = u'cluster_' + profile
208 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
210 cluster_dir_paths = os.environ.get('IPCLUSTER_DIR_PATH','')
209 if cluster_dir_paths:
211 if cluster_dir_paths:
210 cluster_dir_paths = cluster_dir_paths.split(':')
212 cluster_dir_paths = cluster_dir_paths.split(':')
211 else:
213 else:
212 cluster_dir_paths = []
214 cluster_dir_paths = []
213 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
215 paths = [os.getcwd(), ipython_dir] + cluster_dir_paths
214 for p in paths:
216 for p in paths:
215 cluster_dir = os.path.join(p, dirname)
217 cluster_dir = os.path.join(p, dirname)
216 if os.path.isdir(cluster_dir):
218 if os.path.isdir(cluster_dir):
217 return ClusterDir(cluster_dir)
219 return ClusterDir(cluster_dir)
218 else:
220 else:
219 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
221 raise ClusterDirError('Cluster directory not found in paths: %s' % dirname)
220
222
221 @classmethod
223 @classmethod
222 def find_cluster_dir(cls, cluster_dir):
224 def find_cluster_dir(cls, cluster_dir):
223 """Find/create a cluster dir and return its ClusterDir.
225 """Find/create a cluster dir and return its ClusterDir.
224
226
225 This will create the cluster directory if it doesn't exist.
227 This will create the cluster directory if it doesn't exist.
226
228
227 Parameters
229 Parameters
228 ----------
230 ----------
229 cluster_dir : unicode or str
231 cluster_dir : unicode or str
230 The path of the cluster directory. This is expanded using
232 The path of the cluster directory. This is expanded using
231 :func:`IPython.utils.genutils.expand_path`.
233 :func:`IPython.utils.genutils.expand_path`.
232 """
234 """
233 cluster_dir = expand_path(cluster_dir)
235 cluster_dir = expand_path(cluster_dir)
234 if not os.path.isdir(cluster_dir):
236 if not os.path.isdir(cluster_dir):
235 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
237 raise ClusterDirError('Cluster directory not found: %s' % cluster_dir)
236 return ClusterDir(cluster_dir)
238 return ClusterDir(cluster_dir)
237
239
238
240
239 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
240 # Command line options
242 # Command line options
241 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
242
244
243 class ClusterDirConfigLoader(BaseAppConfigLoader):
245 class ClusterDirConfigLoader(BaseAppConfigLoader):
244
246
245 def _add_cluster_profile(self, parser):
247 def _add_cluster_profile(self, parser):
246 paa = parser.add_argument
248 paa = parser.add_argument
247 paa('-p', '--profile',
249 paa('-p', '--profile',
248 dest='Global.profile',type=unicode,
250 dest='Global.profile',type=unicode,
249 help=
251 help=
250 """The string name of the profile to be used. This determines the name
252 """The string name of the profile to be used. This determines the name
251 of the cluster dir as: cluster_<profile>. The default profile is named
253 of the cluster dir as: cluster_<profile>. The default profile is named
252 'default'. The cluster directory is resolve this way if the
254 'default'. The cluster directory is resolve this way if the
253 --cluster-dir option is not used.""",
255 --cluster-dir option is not used.""",
254 metavar='Global.profile')
256 metavar='Global.profile')
255
257
256 def _add_cluster_dir(self, parser):
258 def _add_cluster_dir(self, parser):
257 paa = parser.add_argument
259 paa = parser.add_argument
258 paa('--cluster-dir',
260 paa('--cluster-dir',
259 dest='Global.cluster_dir',type=unicode,
261 dest='Global.cluster_dir',type=unicode,
260 help="""Set the cluster dir. This overrides the logic used by the
262 help="""Set the cluster dir. This overrides the logic used by the
261 --profile option.""",
263 --profile option.""",
262 metavar='Global.cluster_dir')
264 metavar='Global.cluster_dir')
263
265
264 def _add_work_dir(self, parser):
266 def _add_work_dir(self, parser):
265 paa = parser.add_argument
267 paa = parser.add_argument
266 paa('--work-dir',
268 paa('--work-dir',
267 dest='Global.work_dir',type=unicode,
269 dest='Global.work_dir',type=unicode,
268 help='Set the working dir for the process.',
270 help='Set the working dir for the process.',
269 metavar='Global.work_dir')
271 metavar='Global.work_dir')
270
272
271 def _add_clean_logs(self, parser):
273 def _add_clean_logs(self, parser):
272 paa = parser.add_argument
274 paa = parser.add_argument
273 paa('--clean-logs',
275 paa('--clean-logs',
274 dest='Global.clean_logs', action='store_true',
276 dest='Global.clean_logs', action='store_true',
275 help='Delete old log flies before starting.')
277 help='Delete old log flies before starting.')
276
278
277 def _add_no_clean_logs(self, parser):
279 def _add_no_clean_logs(self, parser):
278 paa = parser.add_argument
280 paa = parser.add_argument
279 paa('--no-clean-logs',
281 paa('--no-clean-logs',
280 dest='Global.clean_logs', action='store_false',
282 dest='Global.clean_logs', action='store_false',
281 help="Don't Delete old log flies before starting.")
283 help="Don't Delete old log flies before starting.")
282
284
283 def _add_arguments(self):
285 def _add_arguments(self):
284 super(ClusterDirConfigLoader, self)._add_arguments()
286 super(ClusterDirConfigLoader, self)._add_arguments()
285 self._add_cluster_profile(self.parser)
287 self._add_cluster_profile(self.parser)
286 self._add_cluster_dir(self.parser)
288 self._add_cluster_dir(self.parser)
287 self._add_work_dir(self.parser)
289 self._add_work_dir(self.parser)
288 self._add_clean_logs(self.parser)
290 self._add_clean_logs(self.parser)
289 self._add_no_clean_logs(self.parser)
291 self._add_no_clean_logs(self.parser)
290
292
291
293
292 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
295 # Crash handler for this application
296 #-----------------------------------------------------------------------------
297
298
299 _message_template = """\
300 Oops, $self.app_name crashed. We do our best to make it stable, but...
301
302 A crash report was automatically generated with the following information:
303 - A verbatim copy of the crash traceback.
304 - Data on your current $self.app_name configuration.
305
306 It was left in the file named:
307 \t'$self.crash_report_fname'
308 If you can email this file to the developers, the information in it will help
309 them in understanding and correcting the problem.
310
311 You can mail it to: $self.contact_name at $self.contact_email
312 with the subject '$self.app_name Crash Report'.
313
314 If you want to do it now, the following command will work (under Unix):
315 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
316
317 To ensure accurate tracking of this issue, please file a report about it at:
318 $self.bug_tracker
319 """
320
321 class ClusterDirCrashHandler(CrashHandler):
322 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
323
324 message_template = _message_template
325
326 def __init__(self, app):
327 contact_name = release.authors['Brian'][0]
328 contact_email = release.authors['Brian'][1]
329 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
330 super(ClusterDirCrashHandler,self).__init__(
331 app, contact_name, contact_email, bug_tracker
332 )
333
334
335 #-----------------------------------------------------------------------------
293 # Main application
336 # Main application
294 #-----------------------------------------------------------------------------
337 #-----------------------------------------------------------------------------
295
338
296 class ApplicationWithClusterDir(Application):
339 class ApplicationWithClusterDir(Application):
297 """An application that puts everything into a cluster directory.
340 """An application that puts everything into a cluster directory.
298
341
299 Instead of looking for things in the ipython_dir, this type of application
342 Instead of looking for things in the ipython_dir, this type of application
300 will use its own private directory called the "cluster directory"
343 will use its own private directory called the "cluster directory"
301 for things like config files, log files, etc.
344 for things like config files, log files, etc.
302
345
303 The cluster directory is resolved as follows:
346 The cluster directory is resolved as follows:
304
347
305 * If the ``--cluster-dir`` option is given, it is used.
348 * If the ``--cluster-dir`` option is given, it is used.
306 * If ``--cluster-dir`` is not given, the application directory is
349 * If ``--cluster-dir`` is not given, the application directory is
307 resolve using the profile name as ``cluster_<profile>``. The search
350 resolve using the profile name as ``cluster_<profile>``. The search
308 path for this directory is then i) cwd if it is found there
351 path for this directory is then i) cwd if it is found there
309 and ii) in ipython_dir otherwise.
352 and ii) in ipython_dir otherwise.
310
353
311 The config file for the application is to be put in the cluster
354 The config file for the application is to be put in the cluster
312 dir and named the value of the ``config_file_name`` class attribute.
355 dir and named the value of the ``config_file_name`` class attribute.
313 """
356 """
314
357
315 command_line_loader = ClusterDirConfigLoader
358 command_line_loader = ClusterDirConfigLoader
359 crash_handler_class = ClusterDirCrashHandler
316 auto_create_cluster_dir = True
360 auto_create_cluster_dir = True
317
361
318 def create_default_config(self):
362 def create_default_config(self):
319 super(ApplicationWithClusterDir, self).create_default_config()
363 super(ApplicationWithClusterDir, self).create_default_config()
320 self.default_config.Global.profile = u'default'
364 self.default_config.Global.profile = u'default'
321 self.default_config.Global.cluster_dir = u''
365 self.default_config.Global.cluster_dir = u''
322 self.default_config.Global.work_dir = os.getcwd()
366 self.default_config.Global.work_dir = os.getcwd()
323 self.default_config.Global.log_to_file = False
367 self.default_config.Global.log_to_file = False
324 self.default_config.Global.clean_logs = False
368 self.default_config.Global.clean_logs = False
325
369
326 def find_resources(self):
370 def find_resources(self):
327 """This resolves the cluster directory.
371 """This resolves the cluster directory.
328
372
329 This tries to find the cluster directory and if successful, it will
373 This tries to find the cluster directory and if successful, it will
330 have done:
374 have done:
331 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
375 * Sets ``self.cluster_dir_obj`` to the :class:`ClusterDir` object for
332 the application.
376 the application.
333 * Sets ``self.cluster_dir`` attribute of the application and config
377 * Sets ``self.cluster_dir`` attribute of the application and config
334 objects.
378 objects.
335
379
336 The algorithm used for this is as follows:
380 The algorithm used for this is as follows:
337 1. Try ``Global.cluster_dir``.
381 1. Try ``Global.cluster_dir``.
338 2. Try using ``Global.profile``.
382 2. Try using ``Global.profile``.
339 3. If both of these fail and ``self.auto_create_cluster_dir`` is
383 3. If both of these fail and ``self.auto_create_cluster_dir`` is
340 ``True``, then create the new cluster dir in the IPython directory.
384 ``True``, then create the new cluster dir in the IPython directory.
341 4. If all fails, then raise :class:`ClusterDirError`.
385 4. If all fails, then raise :class:`ClusterDirError`.
342 """
386 """
343
387
344 try:
388 try:
345 cluster_dir = self.command_line_config.Global.cluster_dir
389 cluster_dir = self.command_line_config.Global.cluster_dir
346 except AttributeError:
390 except AttributeError:
347 cluster_dir = self.default_config.Global.cluster_dir
391 cluster_dir = self.default_config.Global.cluster_dir
348 cluster_dir = expand_path(cluster_dir)
392 cluster_dir = expand_path(cluster_dir)
349 try:
393 try:
350 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
394 self.cluster_dir_obj = ClusterDir.find_cluster_dir(cluster_dir)
351 except ClusterDirError:
395 except ClusterDirError:
352 pass
396 pass
353 else:
397 else:
354 self.log.info('Using existing cluster dir: %s' % \
398 self.log.info('Using existing cluster dir: %s' % \
355 self.cluster_dir_obj.location
399 self.cluster_dir_obj.location
356 )
400 )
357 self.finish_cluster_dir()
401 self.finish_cluster_dir()
358 return
402 return
359
403
360 try:
404 try:
361 self.profile = self.command_line_config.Global.profile
405 self.profile = self.command_line_config.Global.profile
362 except AttributeError:
406 except AttributeError:
363 self.profile = self.default_config.Global.profile
407 self.profile = self.default_config.Global.profile
364 try:
408 try:
365 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
409 self.cluster_dir_obj = ClusterDir.find_cluster_dir_by_profile(
366 self.ipython_dir, self.profile)
410 self.ipython_dir, self.profile)
367 except ClusterDirError:
411 except ClusterDirError:
368 pass
412 pass
369 else:
413 else:
370 self.log.info('Using existing cluster dir: %s' % \
414 self.log.info('Using existing cluster dir: %s' % \
371 self.cluster_dir_obj.location
415 self.cluster_dir_obj.location
372 )
416 )
373 self.finish_cluster_dir()
417 self.finish_cluster_dir()
374 return
418 return
375
419
376 if self.auto_create_cluster_dir:
420 if self.auto_create_cluster_dir:
377 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
421 self.cluster_dir_obj = ClusterDir.create_cluster_dir_by_profile(
378 self.ipython_dir, self.profile
422 self.ipython_dir, self.profile
379 )
423 )
380 self.log.info('Creating new cluster dir: %s' % \
424 self.log.info('Creating new cluster dir: %s' % \
381 self.cluster_dir_obj.location
425 self.cluster_dir_obj.location
382 )
426 )
383 self.finish_cluster_dir()
427 self.finish_cluster_dir()
384 else:
428 else:
385 raise ClusterDirError('Could not find a valid cluster directory.')
429 raise ClusterDirError('Could not find a valid cluster directory.')
386
430
387 def finish_cluster_dir(self):
431 def finish_cluster_dir(self):
388 # Set the cluster directory
432 # Set the cluster directory
389 self.cluster_dir = self.cluster_dir_obj.location
433 self.cluster_dir = self.cluster_dir_obj.location
390
434
391 # These have to be set because they could be different from the one
435 # These have to be set because they could be different from the one
392 # that we just computed. Because command line has the highest
436 # that we just computed. Because command line has the highest
393 # priority, this will always end up in the master_config.
437 # priority, this will always end up in the master_config.
394 self.default_config.Global.cluster_dir = self.cluster_dir
438 self.default_config.Global.cluster_dir = self.cluster_dir
395 self.command_line_config.Global.cluster_dir = self.cluster_dir
439 self.command_line_config.Global.cluster_dir = self.cluster_dir
396
440
397 def find_config_file_name(self):
441 def find_config_file_name(self):
398 """Find the config file name for this application."""
442 """Find the config file name for this application."""
399 # For this type of Application it should be set as a class attribute.
443 # For this type of Application it should be set as a class attribute.
400 if not hasattr(self, 'config_file_name'):
444 if not hasattr(self, 'config_file_name'):
401 self.log.critical("No config filename found")
445 self.log.critical("No config filename found")
402
446
403 def find_config_file_paths(self):
447 def find_config_file_paths(self):
404 # Set the search path to to the cluster directory. We should NOT
448 # Set the search path to to the cluster directory. We should NOT
405 # include IPython.config.default here as the default config files
449 # include IPython.config.default here as the default config files
406 # are ALWAYS automatically moved to the cluster directory.
450 # are ALWAYS automatically moved to the cluster directory.
407 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
451 conf_dir = os.path.join(get_ipython_package_dir(), 'config', 'default')
408 self.config_file_paths = (self.cluster_dir,)
452 self.config_file_paths = (self.cluster_dir,)
409
453
410 def pre_construct(self):
454 def pre_construct(self):
411 # The log and security dirs were set earlier, but here we put them
455 # The log and security dirs were set earlier, but here we put them
412 # into the config and log them.
456 # into the config and log them.
413 config = self.master_config
457 config = self.master_config
414 sdir = self.cluster_dir_obj.security_dir
458 sdir = self.cluster_dir_obj.security_dir
415 self.security_dir = config.Global.security_dir = sdir
459 self.security_dir = config.Global.security_dir = sdir
416 ldir = self.cluster_dir_obj.log_dir
460 ldir = self.cluster_dir_obj.log_dir
417 self.log_dir = config.Global.log_dir = ldir
461 self.log_dir = config.Global.log_dir = ldir
418 pdir = self.cluster_dir_obj.pid_dir
462 pdir = self.cluster_dir_obj.pid_dir
419 self.pid_dir = config.Global.pid_dir = pdir
463 self.pid_dir = config.Global.pid_dir = pdir
420 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
464 self.log.info("Cluster directory set to: %s" % self.cluster_dir)
421 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
465 config.Global.work_dir = unicode(expand_path(config.Global.work_dir))
422 # Change to the working directory. We do this just before construct
466 # Change to the working directory. We do this just before construct
423 # is called so all the components there have the right working dir.
467 # is called so all the components there have the right working dir.
424 self.to_work_dir()
468 self.to_work_dir()
425
469
426 def to_work_dir(self):
470 def to_work_dir(self):
427 wd = self.master_config.Global.work_dir
471 wd = self.master_config.Global.work_dir
428 if unicode(wd) != unicode(os.getcwd()):
472 if unicode(wd) != unicode(os.getcwd()):
429 os.chdir(wd)
473 os.chdir(wd)
430 self.log.info("Changing to working dir: %s" % wd)
474 self.log.info("Changing to working dir: %s" % wd)
431
475
432 def start_logging(self):
476 def start_logging(self):
433 # Remove old log files
477 # Remove old log files
434 if self.master_config.Global.clean_logs:
478 if self.master_config.Global.clean_logs:
435 log_dir = self.master_config.Global.log_dir
479 log_dir = self.master_config.Global.log_dir
436 for f in os.listdir(log_dir):
480 for f in os.listdir(log_dir):
437 if f.startswith(self.name + u'-') and f.endswith('.log'):
481 if f.startswith(self.name + u'-') and f.endswith('.log'):
438 os.remove(os.path.join(log_dir, f))
482 os.remove(os.path.join(log_dir, f))
439 # Start logging to the new log file
483 # Start logging to the new log file
440 if self.master_config.Global.log_to_file:
484 if self.master_config.Global.log_to_file:
441 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
485 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
442 logfile = os.path.join(self.log_dir, log_filename)
486 logfile = os.path.join(self.log_dir, log_filename)
443 open_log_file = open(logfile, 'w')
487 open_log_file = open(logfile, 'w')
444 else:
488 else:
445 open_log_file = sys.stdout
489 open_log_file = sys.stdout
446 log.startLogging(open_log_file)
490 log.startLogging(open_log_file)
447
491
448 def write_pid_file(self, overwrite=False):
492 def write_pid_file(self, overwrite=False):
449 """Create a .pid file in the pid_dir with my pid.
493 """Create a .pid file in the pid_dir with my pid.
450
494
451 This must be called after pre_construct, which sets `self.pid_dir`.
495 This must be called after pre_construct, which sets `self.pid_dir`.
452 This raises :exc:`PIDFileError` if the pid file exists already.
496 This raises :exc:`PIDFileError` if the pid file exists already.
453 """
497 """
454 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
498 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
455 if os.path.isfile(pid_file):
499 if os.path.isfile(pid_file):
456 pid = self.get_pid_from_file()
500 pid = self.get_pid_from_file()
457 if not overwrite:
501 if not overwrite:
458 raise PIDFileError(
502 raise PIDFileError(
459 'The pid file [%s] already exists. \nThis could mean that this '
503 'The pid file [%s] already exists. \nThis could mean that this '
460 'server is already running with [pid=%s].' % (pid_file, pid)
504 'server is already running with [pid=%s].' % (pid_file, pid)
461 )
505 )
462 with open(pid_file, 'w') as f:
506 with open(pid_file, 'w') as f:
463 self.log.info("Creating pid file: %s" % pid_file)
507 self.log.info("Creating pid file: %s" % pid_file)
464 f.write(repr(os.getpid())+'\n')
508 f.write(repr(os.getpid())+'\n')
465
509
466 def remove_pid_file(self):
510 def remove_pid_file(self):
467 """Remove the pid file.
511 """Remove the pid file.
468
512
469 This should be called at shutdown by registering a callback with
513 This should be called at shutdown by registering a callback with
470 :func:`reactor.addSystemEventTrigger`. This needs to return
514 :func:`reactor.addSystemEventTrigger`. This needs to return
471 ``None``.
515 ``None``.
472 """
516 """
473 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
517 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
474 if os.path.isfile(pid_file):
518 if os.path.isfile(pid_file):
475 try:
519 try:
476 self.log.info("Removing pid file: %s" % pid_file)
520 self.log.info("Removing pid file: %s" % pid_file)
477 os.remove(pid_file)
521 os.remove(pid_file)
478 except:
522 except:
479 self.log.warn("Error removing the pid file: %s" % pid_file)
523 self.log.warn("Error removing the pid file: %s" % pid_file)
480
524
481 def get_pid_from_file(self):
525 def get_pid_from_file(self):
482 """Get the pid from the pid file.
526 """Get the pid from the pid file.
483
527
484 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
528 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
485 """
529 """
486 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
530 pid_file = os.path.join(self.pid_dir, self.name + u'.pid')
487 if os.path.isfile(pid_file):
531 if os.path.isfile(pid_file):
488 with open(pid_file, 'r') as f:
532 with open(pid_file, 'r') as f:
489 pid = int(f.read().strip())
533 pid = int(f.read().strip())
490 return pid
534 return pid
491 else:
535 else:
492 raise PIDFileError('pid file not found: %s' % pid_file)
536 raise PIDFileError('pid file not found: %s' % pid_file)
493
537
General Comments 0
You need to be logged in to leave comments. Login now