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