~ipython-contrib/ipython/traitlets-rename

« back to all changes in this revision

Viewing changes to IPython/core/application.py

  • Committer: Dav Clark
  • Date: 2010-01-14 04:02:25 UTC
  • mfrom: (1102.1.220 trunk-dev)
  • Revision ID: davclark@berkeley.edu-20100114040225-dl8eyu6eao2oszra
mergedĀ fromĀ ~fdo.perez/ipython/trunk-dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
1
# encoding: utf-8
3
2
"""
4
 
An application for IPython
 
3
An application for IPython.
 
4
 
 
5
All top-level applications should use the classes in this module for
 
6
handling configuration and creating componenets.
 
7
 
 
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.
5
10
 
6
11
Authors:
7
12
 
26
31
import logging
27
32
import os
28
33
import sys
29
 
import traceback
30
 
from copy import deepcopy
31
34
 
32
 
from IPython.utils.genutils import get_ipython_dir, filefind
 
35
from IPython.core import release, crashhandler
 
36
from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
33
37
from IPython.config.loader import (
34
38
    PyFileConfigLoader,
35
39
    ArgParseConfigLoader,
36
40
    Config,
37
 
    NoConfigDefault
38
41
)
39
42
 
40
43
#-----------------------------------------------------------------------------
41
44
# Classes and functions
42
45
#-----------------------------------------------------------------------------
43
46
 
44
 
 
45
 
class IPythonArgParseConfigLoader(ArgParseConfigLoader):
46
 
    """Default command line options for IPython based applications."""
47
 
 
48
 
    def _add_other_arguments(self):
49
 
        self.parser.add_argument('-ipythondir',dest='Global.ipythondir',type=str,
50
 
            help='Set to override default location of Global.ipythondir.',
51
 
            default=NoConfigDefault,
52
 
            metavar='Global.ipythondir')
53
 
        self.parser.add_argument('-p','-profile',dest='Global.profile',type=str,
54
 
            help='The string name of the ipython profile to be used.',
55
 
            default=NoConfigDefault,
56
 
            metavar='Global.profile')
57
 
        self.parser.add_argument('-log_level',dest="Global.log_level",type=int,
58
 
            help='Set the log level (0,10,20,30,40,50).  Default is 30.',
59
 
            default=NoConfigDefault)
60
 
        self.parser.add_argument('-config_file',dest='Global.config_file',type=str,
61
 
            help='Set the config file name to override default.',
62
 
            default=NoConfigDefault,
63
 
            metavar='Global.config_file')
64
 
 
65
 
 
66
47
class ApplicationError(Exception):
67
48
    pass
68
49
 
69
50
 
 
51
app_cl_args = (
 
52
    (('--ipython-dir', ), dict(
 
53
        dest='Global.ipython_dir',type=unicode,
 
54
        help=
 
55
        """Set to override default location of the IPython directory
 
56
        IPYTHON_DIR, stored as Global.ipython_dir.  This can also be specified
 
57
        through the environment variable IPYTHON_DIR.""",
 
58
        metavar='Global.ipython_dir') ),
 
59
    (('-p', '--profile',), dict(
 
60
        dest='Global.profile',type=unicode,
 
61
        help=
 
62
        """The string name of the ipython profile to be used.  Assume that your
 
63
        config file is ipython_config-<name>.py (looks in current dir first,
 
64
        then in IPYTHON_DIR). This is a quick way to keep and load multiple
 
65
        config files for different tasks, especially if include your basic one
 
66
        in your more specialized ones.  You can keep a basic
 
67
        IPYTHON_DIR/ipython_config.py file and then have other 'profiles' which
 
68
        include this one and load extra things for particular tasks.""",
 
69
        metavar='Global.profile') ),
 
70
    (('--log-level',), dict(
 
71
        dest="Global.log_level",type=int,
 
72
        help='Set the log level (0,10,20,30,40,50).  Default is 30.',
 
73
        metavar='Global.log_level')),
 
74
    (('--config-file',), dict(
 
75
        dest='Global.config_file',type=unicode,
 
76
        help=
 
77
        """Set the config file name to override default.  Normally IPython
 
78
        loads ipython_config.py (from current directory) or
 
79
        IPYTHON_DIR/ipython_config.py.  If the loading of your config file
 
80
        fails, IPython starts with a bare bones configuration (no modules
 
81
        loaded at all).""",
 
82
        metavar='Global.config_file')),
 
83
    )
 
84
 
70
85
class Application(object):
71
 
    """Load a config, construct an app and run it.
 
86
    """Load a config, construct components and set them running.
 
87
 
 
88
    The configuration of an application can be done via four different Config
 
89
    objects, which are loaded and ultimately merged into a single one used from
 
90
    that point on by the app.  These are:
 
91
 
 
92
    1. default_config: internal defaults, implemented in code.
 
93
    2. file_config: read from the filesystem.
 
94
    3. command_line_config: read from the system's command line flags.
 
95
    4. constructor_config: passed parametrically to the constructor.
 
96
 
 
97
    During initialization, 3 is actually read before 2, since at the
 
98
    command-line one may override the location of the file to be read.  But the
 
99
    above is the order in which the merge is made.
 
100
 
 
101
    There is a final config object can be created and passed to the
 
102
    constructor: override_config.  If it exists, this completely overrides the
 
103
    configs 2-4 above (the default is still used to ensure that all needed
 
104
    fields at least are created).  This makes it easier to create
 
105
    parametrically (e.g. in testing or sphinx plugins) objects with a known
 
106
    configuration, that are unaffected by whatever arguments may be present in
 
107
    sys.argv or files in the user's various directories.
72
108
    """
73
109
 
74
 
    config_file_name = 'ipython_config.py'
75
 
    name = 'ipython'
76
 
 
77
 
    def __init__(self):
 
110
    name = u'ipython'
 
111
    description = 'IPython: an enhanced interactive Python shell.'
 
112
    #: usage message printed by argparse. If None, auto-generate
 
113
    usage = None
 
114
    config_file_name = u'ipython_config.py'
 
115
    #: Track the default and actual separately because some messages are
 
116
    #: only printed if we aren't using the default.
 
117
    default_config_file_name = config_file_name
 
118
    default_log_level = logging.WARN
 
119
    #: Set by --profile option
 
120
    profile_name = None
 
121
    #: User's ipython directory, typically ~/.ipython/
 
122
    ipython_dir = None
 
123
    #: internal defaults, implemented in code.
 
124
    default_config = None
 
125
    #: read from the filesystem
 
126
    file_config = None
 
127
    #: read from the system's command line flags
 
128
    command_line_config = None
 
129
    #: passed parametrically to the constructor.
 
130
    constructor_config = None
 
131
    #: final override, if given supercedes file/command/constructor configs
 
132
    override_config = None
 
133
    #: A reference to the argv to be used (typically ends up being sys.argv[1:])
 
134
    argv = None
 
135
    #: Default command line arguments.  Subclasses should create a new tuple
 
136
    #: that *includes* these.
 
137
    cl_arguments = app_cl_args
 
138
 
 
139
    #: extra arguments computed by the command-line loader
 
140
    extra_args = None
 
141
 
 
142
    # Private attributes
 
143
    _exiting = False
 
144
    _initialized = False
 
145
 
 
146
    # Class choices for things that will be instantiated at runtime.
 
147
    _CrashHandler = crashhandler.CrashHandler
 
148
 
 
149
    def __init__(self, argv=None, constructor_config=None, override_config=None):
 
150
        self.argv = sys.argv[1:] if argv is None else argv
 
151
        self.constructor_config = constructor_config
 
152
        self.override_config = override_config
78
153
        self.init_logger()
79
 
        self.default_config_file_name = self.config_file_name
80
154
 
81
155
    def init_logger(self):
82
156
        self.log = logging.getLogger(self.__class__.__name__)
83
157
        # This is used as the default until the command line arguments are read.
84
 
        self.log.setLevel(logging.WARN)
 
158
        self.log.setLevel(self.default_log_level)
85
159
        self._log_handler = logging.StreamHandler()
86
160
        self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
87
161
        self._log_handler.setFormatter(self._log_formatter)
95
169
 
96
170
    log_level = property(_get_log_level, _set_log_level)
97
171
 
 
172
    def initialize(self):
 
173
        """Initialize the application.
 
174
 
 
175
        Loads all configuration information and sets all application state, but
 
176
        does not start any relevant processing (typically some kind of event
 
177
        loop).
 
178
 
 
179
        Once this method has been called, the application is flagged as
 
180
        initialized and the method becomes a no-op."""
 
181
        
 
182
        if self._initialized:
 
183
            return
 
184
 
 
185
        # The first part is protected with an 'attempt' wrapper, that will log
 
186
        # failures with the basic system traceback machinery.  Once our crash
 
187
        # handler is in place, we can let any subsequent exception propagate,
 
188
        # as our handler will log it with much better detail than the default.
 
189
        self.attempt(self.create_crash_handler)
 
190
 
 
191
        # Configuration phase
 
192
        # Default config (internally hardwired in application code)
 
193
        self.create_default_config()
 
194
        self.log_default_config()
 
195
        self.set_default_config_log_level()
 
196
 
 
197
        if self.override_config is None:
 
198
            # Command-line config
 
199
            self.pre_load_command_line_config()
 
200
            self.load_command_line_config()
 
201
            self.set_command_line_config_log_level()
 
202
            self.post_load_command_line_config()
 
203
            self.log_command_line_config()
 
204
 
 
205
        # Find resources needed for filesystem access, using information from
 
206
        # the above two
 
207
        self.find_ipython_dir()
 
208
        self.find_resources()
 
209
        self.find_config_file_name()
 
210
        self.find_config_file_paths()
 
211
 
 
212
        if self.override_config is None:
 
213
            # File-based config
 
214
            self.pre_load_file_config()
 
215
            self.load_file_config()
 
216
            self.set_file_config_log_level()
 
217
            self.post_load_file_config()
 
218
            self.log_file_config()
 
219
 
 
220
        # Merge all config objects into a single one the app can then use
 
221
        self.merge_configs()
 
222
        self.log_master_config()
 
223
 
 
224
        # Construction phase
 
225
        self.pre_construct()
 
226
        self.construct()
 
227
        self.post_construct()
 
228
 
 
229
        # Done, flag as such and
 
230
        self._initialized = True
 
231
 
98
232
    def start(self):
99
233
        """Start the application."""
100
 
        self.attempt(self.create_default_config)
101
 
        self.attempt(self.pre_load_command_line_config)
102
 
        self.attempt(self.load_command_line_config, action='abort')
103
 
        self.attempt(self.post_load_command_line_config)
104
 
        self.attempt(self.find_ipythondir)
105
 
        self.attempt(self.find_config_file_name)
106
 
        self.attempt(self.find_config_file_paths)
107
 
        self.attempt(self.pre_load_file_config)
108
 
        self.attempt(self.load_file_config)
109
 
        self.attempt(self.post_load_file_config)
110
 
        self.attempt(self.merge_configs)
111
 
        self.attempt(self.pre_construct)
112
 
        self.attempt(self.construct)
113
 
        self.attempt(self.post_construct)
114
 
        self.attempt(self.start_app)
 
234
        self.initialize()
 
235
        self.start_app()
115
236
 
116
237
    #-------------------------------------------------------------------------
117
238
    # Various stages of Application creation
118
239
    #-------------------------------------------------------------------------
119
240
 
 
241
    def create_crash_handler(self):
 
242
        """Create a crash handler, typically setting sys.excepthook to it."""
 
243
        self.crash_handler = self._CrashHandler(self, self.name)
 
244
        sys.excepthook = self.crash_handler
 
245
 
120
246
    def create_default_config(self):
121
247
        """Create defaults that can't be set elsewhere.
122
248
 
126
252
        we set them here.  The Global section is for variables like this that
127
253
        don't belong to a particular component.
128
254
        """
129
 
        self.default_config = Config()
130
 
        self.default_config.Global.ipythondir = get_ipython_dir()
 
255
        c = Config()
 
256
        c.Global.ipython_dir = get_ipython_dir()
 
257
        c.Global.log_level = self.log_level
 
258
        self.default_config = c
 
259
 
 
260
    def log_default_config(self):
131
261
        self.log.debug('Default config loaded:')
132
262
        self.log.debug(repr(self.default_config))
133
263
 
 
264
    def set_default_config_log_level(self):
 
265
        try:
 
266
            self.log_level = self.default_config.Global.log_level
 
267
        except AttributeError:
 
268
            # Fallback to the default_log_level class attribute
 
269
            pass
 
270
 
134
271
    def create_command_line_config(self):
135
272
        """Create and return a command line config loader."""
136
 
        return IPythonArgParseConfigLoader(description=self.name)
 
273
        return ArgParseConfigLoader(self.argv, self.cl_arguments,
 
274
                                    description=self.description, 
 
275
                                    version=release.version,
 
276
                                    usage=self.usage,
 
277
                                    )
137
278
 
138
279
    def pre_load_command_line_config(self):
139
280
        """Do actions just before loading the command line config."""
140
281
        pass
141
282
 
142
283
    def load_command_line_config(self):
143
 
        """Load the command line config.
144
 
 
145
 
        This method also sets ``self.debug``.
146
 
        """
147
 
 
 
284
        """Load the command line config."""
148
285
        loader = self.create_command_line_config()
149
286
        self.command_line_config = loader.load_config()
150
287
        self.extra_args = loader.get_extra_args()
151
288
 
 
289
    def set_command_line_config_log_level(self):
152
290
        try:
153
291
            self.log_level = self.command_line_config.Global.log_level
154
292
        except AttributeError:
155
 
            pass # Use existing value which is set in Application.init_logger.
 
293
            pass
 
294
 
 
295
    def post_load_command_line_config(self):
 
296
        """Do actions just after loading the command line config."""
 
297
        pass
 
298
 
 
299
    def log_command_line_config(self):
156
300
        self.log.debug("Command line config loaded:")
157
301
        self.log.debug(repr(self.command_line_config))
158
302
 
159
 
    def post_load_command_line_config(self):
160
 
        """Do actions just after loading the command line config."""
161
 
        pass
162
 
 
163
 
    def find_ipythondir(self):
 
303
    def find_ipython_dir(self):
164
304
        """Set the IPython directory.
165
305
 
166
 
        This sets ``self.ipythondir``, but the actual value that is passed
167
 
        to the application is kept in either ``self.default_config`` or
168
 
        ``self.command_line_config``.  This also added ``self.ipythondir`` to
169
 
        ``sys.path`` so config files there can be references by other config
 
306
        This sets ``self.ipython_dir``, but the actual value that is passed to
 
307
        the application is kept in either ``self.default_config`` or
 
308
        ``self.command_line_config``.  This also adds ``self.ipython_dir`` to
 
309
        ``sys.path`` so config files there can be referenced by other config
170
310
        files.
171
311
        """
172
312
 
173
313
        try:
174
 
            self.ipythondir = self.command_line_config.Global.ipythondir
 
314
            self.ipython_dir = self.command_line_config.Global.ipython_dir
175
315
        except AttributeError:
176
 
            self.ipythondir = self.default_config.Global.ipythondir
177
 
        sys.path.append(os.path.abspath(self.ipythondir))
178
 
        if not os.path.isdir(self.ipythondir):
179
 
            os.makedirs(self.ipythondir, mode = 0777)
180
 
        self.log.debug("IPYTHONDIR set to: %s" % self.ipythondir)
 
316
            self.ipython_dir = self.default_config.Global.ipython_dir
 
317
        sys.path.append(os.path.abspath(self.ipython_dir))
 
318
        if not os.path.isdir(self.ipython_dir):
 
319
            os.makedirs(self.ipython_dir, mode=0777)
 
320
        self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
 
321
 
 
322
    def find_resources(self):
 
323
        """Find other resources that need to be in place.
 
324
 
 
325
        Things like cluster directories need to be in place to find the
 
326
        config file.  These happen right after the IPython directory has
 
327
        been set.
 
328
        """
 
329
        pass
181
330
 
182
331
    def find_config_file_name(self):
183
332
        """Find the config file name for this application.
184
333
 
185
 
        If a profile has been set at the command line, this will resolve
186
 
        it.  The search paths for the config file are set in
187
 
        :meth:`find_config_file_paths` and then passed to the config file
188
 
        loader where they are resolved to an absolute path.
 
334
        This must set ``self.config_file_name`` to the filename of the
 
335
        config file to use (just the filename). The search paths for the
 
336
        config file are set in :meth:`find_config_file_paths` and then passed
 
337
        to the config file loader where they are resolved to an absolute path.
 
338
 
 
339
        If a profile has been set at the command line, this will resolve it.
189
340
        """
190
341
 
191
342
        try:
195
346
 
196
347
        try:
197
348
            self.profile_name = self.command_line_config.Global.profile
 
349
        except AttributeError:
 
350
            pass
 
351
        else:
198
352
            name_parts = self.config_file_name.split('.')
199
 
            name_parts.insert(1, '_' + self.profile_name + '.')
 
353
            name_parts.insert(1, u'_' + self.profile_name + u'.')
200
354
            self.config_file_name = ''.join(name_parts)
201
 
        except AttributeError:
202
 
            pass
203
355
 
204
356
    def find_config_file_paths(self):
205
 
        """Set the search paths for resolving the config file."""
206
 
        self.config_file_paths = (os.getcwd(), self.ipythondir)
 
357
        """Set the search paths for resolving the config file.
 
358
 
 
359
        This must set ``self.config_file_paths`` to a sequence of search
 
360
        paths to pass to the config file loader.
 
361
        """
 
362
        # Include our own profiles directory last, so that users can still find
 
363
        # our shipped copies of builtin profiles even if they don't have them
 
364
        # in their local ipython directory.
 
365
        prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
 
366
        self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
207
367
 
208
368
    def pre_load_file_config(self):
209
369
        """Do actions before the config file is loaded."""
216
376
        ``CONFIG_FILE`` config variable is set to the resolved config file
217
377
        location.  If not successful, an empty config is used.
218
378
        """
219
 
        self.log.debug("Attempting to load config file: <%s>" % self.config_file_name)
 
379
        self.log.debug("Attempting to load config file: %s" %
 
380
                       self.config_file_name)
220
381
        loader = PyFileConfigLoader(self.config_file_name,
221
382
                                    path=self.config_file_paths)
222
383
        try:
225
386
        except IOError:
226
387
            # Only warn if the default config file was NOT being used.
227
388
            if not self.config_file_name==self.default_config_file_name:
228
 
                self.log.warn("Config file not found, skipping: <%s>" % \
 
389
                self.log.warn("Config file not found, skipping: %s" %
229
390
                               self.config_file_name, exc_info=True)
230
391
            self.file_config = Config()
231
392
        except:
232
 
            self.log.warn("Error loading config file: <%s>" % \
233
 
                           self.config_file_name, exc_info=True)
 
393
            self.log.warn("Error loading config file: %s" %
 
394
                          self.config_file_name, exc_info=True)
234
395
            self.file_config = Config()
235
 
        else:
236
 
            self.log.debug("Config file loaded: <%s>" % loader.full_filename)
237
 
            self.log.debug(repr(self.file_config))
 
396
 
 
397
    def set_file_config_log_level(self):
238
398
        # We need to keeep self.log_level updated.  But we only use the value
239
399
        # of the file_config if a value was not specified at the command
240
 
        # line.
 
400
        # line, because the command line overrides everything.
241
401
        if not hasattr(self.command_line_config.Global, 'log_level'):
242
402
            try:
243
403
                self.log_level = self.file_config.Global.log_level
248
408
        """Do actions after the config file is loaded."""
249
409
        pass
250
410
 
 
411
    def log_file_config(self):
 
412
        if hasattr(self.file_config.Global, 'config_file'):
 
413
            self.log.debug("Config file loaded: %s" %
 
414
                           self.file_config.Global.config_file)
 
415
            self.log.debug(repr(self.file_config))
 
416
 
251
417
    def merge_configs(self):
252
418
        """Merge the default, command line and file config objects."""
253
419
        config = Config()
254
420
        config._merge(self.default_config)
255
 
        config._merge(self.file_config)
256
 
        config._merge(self.command_line_config)
 
421
        if self.override_config is None:
 
422
            config._merge(self.file_config)
 
423
            config._merge(self.command_line_config)
 
424
            if self.constructor_config is not None:
 
425
                config._merge(self.constructor_config)
 
426
        else:
 
427
            config._merge(self.override_config)
 
428
        # XXX fperez - propose to Brian we rename master_config to simply
 
429
        # config, I think this is going to be heavily used in examples and
 
430
        # application code and the name is shorter/easier to find/remember.
 
431
        # For now, just alias it...
257
432
        self.master_config = config
 
433
        self.config = config
 
434
 
 
435
    def log_master_config(self):
258
436
        self.log.debug("Master config created:")
259
437
        self.log.debug(repr(self.master_config))
260
438
 
280
458
 
281
459
    def abort(self):
282
460
        """Abort the starting of the application."""
283
 
        self.log.critical("Aborting application: %s" % self.name, exc_info=True)
284
 
        sys.exit(1)
 
461
        if self._exiting:
 
462
            pass
 
463
        else:
 
464
            self.log.critical("Aborting application: %s" % self.name, exc_info=True)
 
465
            self._exiting = True
 
466
            sys.exit(1)
285
467
 
286
 
    def exit(self):
287
 
        self.log.critical("Aborting application: %s" % self.name)
288
 
        sys.exit(1)
 
468
    def exit(self, exit_status=0):
 
469
        if self._exiting:
 
470
            pass
 
471
        else:
 
472
            self.log.debug("Exiting application: %s" % self.name)
 
473
            self._exiting = True
 
474
            sys.exit(exit_status)
289
475
 
290
476
    def attempt(self, func, action='abort'):
291
477
        try:
292
478
            func()
293
479
        except SystemExit:
294
 
            self.exit()
 
480
            raise
295
481
        except:
296
482
            if action == 'abort':
 
483
                self.log.critical("Aborting application: %s" % self.name,
 
484
                                  exc_info=True)
297
485
                self.abort()
 
486
                raise
298
487
            elif action == 'exit':
299
 
                self.exit()
300
 
      
 
488
                self.exit(0)