##// END OF EJS Templates
Minor cleanup for local lookup
Fernando Perez -
Show More
@@ -1,377 +1,378 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 An application for IPython.
4 An application for IPython.
5
5
6 All top-level applications should use the classes in this module for
6 All top-level applications should use the classes in this module for
7 handling configuration and creating componenets.
7 handling configuration and creating componenets.
8
8
9 The job of an :class:`Application` is to create the master configuration
9 The job of an :class:`Application` is to create the master configuration
10 object and then create the components, passing the config to them.
10 object and then create the components, passing the config to them.
11
11
12 Authors:
12 Authors:
13
13
14 * Brian Granger
14 * Brian Granger
15 * Fernando Perez
15 * Fernando Perez
16
16
17 Notes
17 Notes
18 -----
18 -----
19 """
19 """
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Copyright (C) 2008-2009 The IPython Development Team
22 # Copyright (C) 2008-2009 The IPython Development Team
23 #
23 #
24 # Distributed under the terms of the BSD License. The full license is in
24 # Distributed under the terms of the BSD License. The full license is in
25 # the file COPYING, distributed as part of this software.
25 # the file COPYING, distributed as part of this software.
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Imports
29 # Imports
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 import logging
32 import logging
33 import os
33 import os
34 import sys
34 import sys
35
35
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
37 from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
38 from IPython.config.loader import (
38 from IPython.config.loader import (
39 PyFileConfigLoader,
39 PyFileConfigLoader,
40 ArgParseConfigLoader,
40 ArgParseConfigLoader,
41 Config,
41 Config,
42 NoConfigDefault
42 NoConfigDefault
43 )
43 )
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Classes and functions
46 # Classes and functions
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49
49
50 class BaseAppArgParseConfigLoader(ArgParseConfigLoader):
50 class BaseAppArgParseConfigLoader(ArgParseConfigLoader):
51 """Default command line options for IPython based applications."""
51 """Default command line options for IPython based applications."""
52
52
53 def _add_other_arguments(self):
53 def _add_other_arguments(self):
54 self.parser.add_argument('--ipython-dir',
54 self.parser.add_argument('--ipython-dir',
55 dest='Global.ipython_dir',type=unicode,
55 dest='Global.ipython_dir',type=unicode,
56 help='Set to override default location of Global.ipython_dir.',
56 help='Set to override default location of Global.ipython_dir.',
57 default=NoConfigDefault,
57 default=NoConfigDefault,
58 metavar='Global.ipython_dir')
58 metavar='Global.ipython_dir')
59 self.parser.add_argument('-p', '--profile',
59 self.parser.add_argument('-p', '--profile',
60 dest='Global.profile',type=unicode,
60 dest='Global.profile',type=unicode,
61 help='The string name of the ipython profile to be used.',
61 help='The string name of the ipython profile to be used.',
62 default=NoConfigDefault,
62 default=NoConfigDefault,
63 metavar='Global.profile')
63 metavar='Global.profile')
64 self.parser.add_argument('--log-level',
64 self.parser.add_argument('--log-level',
65 dest="Global.log_level",type=int,
65 dest="Global.log_level",type=int,
66 help='Set the log level (0,10,20,30,40,50). Default is 30.',
66 help='Set the log level (0,10,20,30,40,50). Default is 30.',
67 default=NoConfigDefault,
67 default=NoConfigDefault,
68 metavar='Global.log_level')
68 metavar='Global.log_level')
69 self.parser.add_argument('--config-file',
69 self.parser.add_argument('--config-file',
70 dest='Global.config_file',type=unicode,
70 dest='Global.config_file',type=unicode,
71 help='Set the config file name to override default.',
71 help='Set the config file name to override default.',
72 default=NoConfigDefault,
72 default=NoConfigDefault,
73 metavar='Global.config_file')
73 metavar='Global.config_file')
74
74
75
75
76 class ApplicationError(Exception):
76 class ApplicationError(Exception):
77 pass
77 pass
78
78
79
79
80 class Application(object):
80 class Application(object):
81 """Load a config, construct components and set them running."""
81 """Load a config, construct components and set them running."""
82
82
83 name = u'ipython'
83 name = u'ipython'
84 description = 'IPython: an enhanced interactive Python shell.'
84 description = 'IPython: an enhanced interactive Python shell.'
85
85
86 config_file_name = u'ipython_config.py'
86 config_file_name = u'ipython_config.py'
87 # Track the default and actual separately because some messages are
87 # Track the default and actual separately because some messages are
88 # only printed if we aren't using the default.
88 # only printed if we aren't using the default.
89 default_config_file_name = config_file_name
89 default_config_file_name = config_file_name
90 default_log_level = logging.WARN
90 default_log_level = logging.WARN
91 # Set by --profile option
91 # Set by --profile option
92 profile_name = None
92 profile_name = None
93 # User's ipython directory, typically ~/.ipython/
93 # User's ipython directory, typically ~/.ipython/
94 ipython_dir = None
94 ipython_dir = None
95
95
96 # Private attributes
96 # Private attributes
97 _exiting = False
97 _exiting = False
98
98
99 def __init__(self):
99 def __init__(self):
100 self.init_logger()
100 self.init_logger()
101
101
102 def init_logger(self):
102 def init_logger(self):
103 self.log = logging.getLogger(self.__class__.__name__)
103 self.log = logging.getLogger(self.__class__.__name__)
104 # This is used as the default until the command line arguments are read.
104 # This is used as the default until the command line arguments are read.
105 self.log.setLevel(self.default_log_level)
105 self.log.setLevel(self.default_log_level)
106 self._log_handler = logging.StreamHandler()
106 self._log_handler = logging.StreamHandler()
107 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
107 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
108 self._log_handler.setFormatter(self._log_formatter)
108 self._log_handler.setFormatter(self._log_formatter)
109 self.log.addHandler(self._log_handler)
109 self.log.addHandler(self._log_handler)
110
110
111 def _set_log_level(self, level):
111 def _set_log_level(self, level):
112 self.log.setLevel(level)
112 self.log.setLevel(level)
113
113
114 def _get_log_level(self):
114 def _get_log_level(self):
115 return self.log.level
115 return self.log.level
116
116
117 log_level = property(_get_log_level, _set_log_level)
117 log_level = property(_get_log_level, _set_log_level)
118
118
119 def start(self):
119 def start(self):
120 """Start the application."""
120 """Start the application."""
121 self.attempt(self.create_default_config)
121 self.attempt(self.create_default_config)
122 self.log_default_config()
122 self.log_default_config()
123 self.set_default_config_log_level()
123 self.set_default_config_log_level()
124 self.attempt(self.pre_load_command_line_config)
124 self.attempt(self.pre_load_command_line_config)
125 self.attempt(self.load_command_line_config, action='abort')
125 self.attempt(self.load_command_line_config, action='abort')
126 self.set_command_line_config_log_level()
126 self.set_command_line_config_log_level()
127 self.attempt(self.post_load_command_line_config)
127 self.attempt(self.post_load_command_line_config)
128 self.log_command_line_config()
128 self.log_command_line_config()
129 self.attempt(self.find_ipython_dir)
129 self.attempt(self.find_ipython_dir)
130 self.attempt(self.find_resources)
130 self.attempt(self.find_resources)
131 self.attempt(self.find_config_file_name)
131 self.attempt(self.find_config_file_name)
132 self.attempt(self.find_config_file_paths)
132 self.attempt(self.find_config_file_paths)
133 self.attempt(self.pre_load_file_config)
133 self.attempt(self.pre_load_file_config)
134 self.attempt(self.load_file_config)
134 self.attempt(self.load_file_config)
135 self.set_file_config_log_level()
135 self.set_file_config_log_level()
136 self.attempt(self.post_load_file_config)
136 self.attempt(self.post_load_file_config)
137 self.log_file_config()
137 self.log_file_config()
138 self.attempt(self.merge_configs)
138 self.attempt(self.merge_configs)
139 self.log_master_config()
139 self.log_master_config()
140 self.attempt(self.pre_construct)
140 self.attempt(self.pre_construct)
141 self.attempt(self.construct)
141 self.attempt(self.construct)
142 self.attempt(self.post_construct)
142 self.attempt(self.post_construct)
143 self.attempt(self.start_app)
143 self.attempt(self.start_app)
144
144
145 #-------------------------------------------------------------------------
145 #-------------------------------------------------------------------------
146 # Various stages of Application creation
146 # Various stages of Application creation
147 #-------------------------------------------------------------------------
147 #-------------------------------------------------------------------------
148
148
149 def create_default_config(self):
149 def create_default_config(self):
150 """Create defaults that can't be set elsewhere.
150 """Create defaults that can't be set elsewhere.
151
151
152 For the most part, we try to set default in the class attributes
152 For the most part, we try to set default in the class attributes
153 of Components. But, defaults the top-level Application (which is
153 of Components. But, defaults the top-level Application (which is
154 not a HasTraitlets or Component) are not set in this way. Instead
154 not a HasTraitlets or Component) are not set in this way. Instead
155 we set them here. The Global section is for variables like this that
155 we set them here. The Global section is for variables like this that
156 don't belong to a particular component.
156 don't belong to a particular component.
157 """
157 """
158 self.default_config = Config()
158 c = Config()
159 self.default_config.Global.ipython_dir = get_ipython_dir()
159 c.Global.ipython_dir = get_ipython_dir()
160 self.default_config.Global.log_level = self.log_level
160 c.Global.log_level = self.log_level
161 self.default_config = c
161
162
162 def log_default_config(self):
163 def log_default_config(self):
163 self.log.debug('Default config loaded:')
164 self.log.debug('Default config loaded:')
164 self.log.debug(repr(self.default_config))
165 self.log.debug(repr(self.default_config))
165
166
166 def set_default_config_log_level(self):
167 def set_default_config_log_level(self):
167 try:
168 try:
168 self.log_level = self.default_config.Global.log_level
169 self.log_level = self.default_config.Global.log_level
169 except AttributeError:
170 except AttributeError:
170 # Fallback to the default_log_level class attribute
171 # Fallback to the default_log_level class attribute
171 pass
172 pass
172
173
173 def create_command_line_config(self):
174 def create_command_line_config(self):
174 """Create and return a command line config loader."""
175 """Create and return a command line config loader."""
175 return BaseAppArgParseConfigLoader(
176 return BaseAppArgParseConfigLoader(
176 description=self.description,
177 description=self.description,
177 version=release.version
178 version=release.version
178 )
179 )
179
180
180 def pre_load_command_line_config(self):
181 def pre_load_command_line_config(self):
181 """Do actions just before loading the command line config."""
182 """Do actions just before loading the command line config."""
182 pass
183 pass
183
184
184 def load_command_line_config(self):
185 def load_command_line_config(self):
185 """Load the command line config."""
186 """Load the command line config."""
186 loader = self.create_command_line_config()
187 loader = self.create_command_line_config()
187 self.command_line_config = loader.load_config()
188 self.command_line_config = loader.load_config()
188 self.extra_args = loader.get_extra_args()
189 self.extra_args = loader.get_extra_args()
189
190
190 def set_command_line_config_log_level(self):
191 def set_command_line_config_log_level(self):
191 try:
192 try:
192 self.log_level = self.command_line_config.Global.log_level
193 self.log_level = self.command_line_config.Global.log_level
193 except AttributeError:
194 except AttributeError:
194 pass
195 pass
195
196
196 def post_load_command_line_config(self):
197 def post_load_command_line_config(self):
197 """Do actions just after loading the command line config."""
198 """Do actions just after loading the command line config."""
198 pass
199 pass
199
200
200 def log_command_line_config(self):
201 def log_command_line_config(self):
201 self.log.debug("Command line config loaded:")
202 self.log.debug("Command line config loaded:")
202 self.log.debug(repr(self.command_line_config))
203 self.log.debug(repr(self.command_line_config))
203
204
204 def find_ipython_dir(self):
205 def find_ipython_dir(self):
205 """Set the IPython directory.
206 """Set the IPython directory.
206
207
207 This sets ``self.ipython_dir``, but the actual value that is passed to
208 This sets ``self.ipython_dir``, but the actual value that is passed to
208 the application is kept in either ``self.default_config`` or
209 the application is kept in either ``self.default_config`` or
209 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
210 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
210 ``sys.path`` so config files there can be referenced by other config
211 ``sys.path`` so config files there can be referenced by other config
211 files.
212 files.
212 """
213 """
213
214
214 try:
215 try:
215 self.ipython_dir = self.command_line_config.Global.ipython_dir
216 self.ipython_dir = self.command_line_config.Global.ipython_dir
216 except AttributeError:
217 except AttributeError:
217 self.ipython_dir = self.default_config.Global.ipython_dir
218 self.ipython_dir = self.default_config.Global.ipython_dir
218 sys.path.append(os.path.abspath(self.ipython_dir))
219 sys.path.append(os.path.abspath(self.ipython_dir))
219 if not os.path.isdir(self.ipython_dir):
220 if not os.path.isdir(self.ipython_dir):
220 os.makedirs(self.ipython_dir, mode=0777)
221 os.makedirs(self.ipython_dir, mode=0777)
221 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
222 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
222
223
223 def find_resources(self):
224 def find_resources(self):
224 """Find other resources that need to be in place.
225 """Find other resources that need to be in place.
225
226
226 Things like cluster directories need to be in place to find the
227 Things like cluster directories need to be in place to find the
227 config file. These happen right after the IPython directory has
228 config file. These happen right after the IPython directory has
228 been set.
229 been set.
229 """
230 """
230 pass
231 pass
231
232
232 def find_config_file_name(self):
233 def find_config_file_name(self):
233 """Find the config file name for this application.
234 """Find the config file name for this application.
234
235
235 This must set ``self.config_file_name`` to the filename of the
236 This must set ``self.config_file_name`` to the filename of the
236 config file to use (just the filename). The search paths for the
237 config file to use (just the filename). The search paths for the
237 config file are set in :meth:`find_config_file_paths` and then passed
238 config file are set in :meth:`find_config_file_paths` and then passed
238 to the config file loader where they are resolved to an absolute path.
239 to the config file loader where they are resolved to an absolute path.
239
240
240 If a profile has been set at the command line, this will resolve it.
241 If a profile has been set at the command line, this will resolve it.
241 """
242 """
242
243
243 try:
244 try:
244 self.config_file_name = self.command_line_config.Global.config_file
245 self.config_file_name = self.command_line_config.Global.config_file
245 except AttributeError:
246 except AttributeError:
246 pass
247 pass
247
248
248 try:
249 try:
249 self.profile_name = self.command_line_config.Global.profile
250 self.profile_name = self.command_line_config.Global.profile
250 except AttributeError:
251 except AttributeError:
251 pass
252 pass
252 else:
253 else:
253 name_parts = self.config_file_name.split('.')
254 name_parts = self.config_file_name.split('.')
254 name_parts.insert(1, u'_' + self.profile_name + u'.')
255 name_parts.insert(1, u'_' + self.profile_name + u'.')
255 self.config_file_name = ''.join(name_parts)
256 self.config_file_name = ''.join(name_parts)
256
257
257 def find_config_file_paths(self):
258 def find_config_file_paths(self):
258 """Set the search paths for resolving the config file.
259 """Set the search paths for resolving the config file.
259
260
260 This must set ``self.config_file_paths`` to a sequence of search
261 This must set ``self.config_file_paths`` to a sequence of search
261 paths to pass to the config file loader.
262 paths to pass to the config file loader.
262 """
263 """
263 # Include our own profiles directory last, so that users can still find
264 # Include our own profiles directory last, so that users can still find
264 # our shipped copies of builtin profiles even if they don't have them
265 # our shipped copies of builtin profiles even if they don't have them
265 # in their local ipython directory.
266 # in their local ipython directory.
266 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
267 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
267 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
268 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
268
269
269 def pre_load_file_config(self):
270 def pre_load_file_config(self):
270 """Do actions before the config file is loaded."""
271 """Do actions before the config file is loaded."""
271 pass
272 pass
272
273
273 def load_file_config(self):
274 def load_file_config(self):
274 """Load the config file.
275 """Load the config file.
275
276
276 This tries to load the config file from disk. If successful, the
277 This tries to load the config file from disk. If successful, the
277 ``CONFIG_FILE`` config variable is set to the resolved config file
278 ``CONFIG_FILE`` config variable is set to the resolved config file
278 location. If not successful, an empty config is used.
279 location. If not successful, an empty config is used.
279 """
280 """
280 self.log.debug("Attempting to load config file: %s" %
281 self.log.debug("Attempting to load config file: %s" %
281 self.config_file_name)
282 self.config_file_name)
282 loader = PyFileConfigLoader(self.config_file_name,
283 loader = PyFileConfigLoader(self.config_file_name,
283 path=self.config_file_paths)
284 path=self.config_file_paths)
284 try:
285 try:
285 self.file_config = loader.load_config()
286 self.file_config = loader.load_config()
286 self.file_config.Global.config_file = loader.full_filename
287 self.file_config.Global.config_file = loader.full_filename
287 except IOError:
288 except IOError:
288 # Only warn if the default config file was NOT being used.
289 # Only warn if the default config file was NOT being used.
289 if not self.config_file_name==self.default_config_file_name:
290 if not self.config_file_name==self.default_config_file_name:
290 self.log.warn("Config file not found, skipping: %s" %
291 self.log.warn("Config file not found, skipping: %s" %
291 self.config_file_name, exc_info=True)
292 self.config_file_name, exc_info=True)
292 self.file_config = Config()
293 self.file_config = Config()
293 except:
294 except:
294 self.log.warn("Error loading config file: %s" %
295 self.log.warn("Error loading config file: %s" %
295 self.config_file_name, exc_info=True)
296 self.config_file_name, exc_info=True)
296 self.file_config = Config()
297 self.file_config = Config()
297
298
298 def set_file_config_log_level(self):
299 def set_file_config_log_level(self):
299 # We need to keeep self.log_level updated. But we only use the value
300 # We need to keeep self.log_level updated. But we only use the value
300 # of the file_config if a value was not specified at the command
301 # of the file_config if a value was not specified at the command
301 # line, because the command line overrides everything.
302 # line, because the command line overrides everything.
302 if not hasattr(self.command_line_config.Global, 'log_level'):
303 if not hasattr(self.command_line_config.Global, 'log_level'):
303 try:
304 try:
304 self.log_level = self.file_config.Global.log_level
305 self.log_level = self.file_config.Global.log_level
305 except AttributeError:
306 except AttributeError:
306 pass # Use existing value
307 pass # Use existing value
307
308
308 def post_load_file_config(self):
309 def post_load_file_config(self):
309 """Do actions after the config file is loaded."""
310 """Do actions after the config file is loaded."""
310 pass
311 pass
311
312
312 def log_file_config(self):
313 def log_file_config(self):
313 if hasattr(self.file_config.Global, 'config_file'):
314 if hasattr(self.file_config.Global, 'config_file'):
314 self.log.debug("Config file loaded: %s" %
315 self.log.debug("Config file loaded: %s" %
315 self.file_config.Global.config_file)
316 self.file_config.Global.config_file)
316 self.log.debug(repr(self.file_config))
317 self.log.debug(repr(self.file_config))
317
318
318 def merge_configs(self):
319 def merge_configs(self):
319 """Merge the default, command line and file config objects."""
320 """Merge the default, command line and file config objects."""
320 config = Config()
321 config = Config()
321 config._merge(self.default_config)
322 config._merge(self.default_config)
322 config._merge(self.file_config)
323 config._merge(self.file_config)
323 config._merge(self.command_line_config)
324 config._merge(self.command_line_config)
324 self.master_config = config
325 self.master_config = config
325
326
326 def log_master_config(self):
327 def log_master_config(self):
327 self.log.debug("Master config created:")
328 self.log.debug("Master config created:")
328 self.log.debug(repr(self.master_config))
329 self.log.debug(repr(self.master_config))
329
330
330 def pre_construct(self):
331 def pre_construct(self):
331 """Do actions after the config has been built, but before construct."""
332 """Do actions after the config has been built, but before construct."""
332 pass
333 pass
333
334
334 def construct(self):
335 def construct(self):
335 """Construct the main components that make up this app."""
336 """Construct the main components that make up this app."""
336 self.log.debug("Constructing components for application")
337 self.log.debug("Constructing components for application")
337
338
338 def post_construct(self):
339 def post_construct(self):
339 """Do actions after construct, but before starting the app."""
340 """Do actions after construct, but before starting the app."""
340 pass
341 pass
341
342
342 def start_app(self):
343 def start_app(self):
343 """Actually start the app."""
344 """Actually start the app."""
344 self.log.debug("Starting application")
345 self.log.debug("Starting application")
345
346
346 #-------------------------------------------------------------------------
347 #-------------------------------------------------------------------------
347 # Utility methods
348 # Utility methods
348 #-------------------------------------------------------------------------
349 #-------------------------------------------------------------------------
349
350
350 def abort(self):
351 def abort(self):
351 """Abort the starting of the application."""
352 """Abort the starting of the application."""
352 if self._exiting:
353 if self._exiting:
353 pass
354 pass
354 else:
355 else:
355 self.log.critical("Aborting application: %s" % self.name, exc_info=True)
356 self.log.critical("Aborting application: %s" % self.name, exc_info=True)
356 self._exiting = True
357 self._exiting = True
357 sys.exit(1)
358 sys.exit(1)
358
359
359 def exit(self, exit_status=0):
360 def exit(self, exit_status=0):
360 if self._exiting:
361 if self._exiting:
361 pass
362 pass
362 else:
363 else:
363 self.log.debug("Exiting application: %s" % self.name)
364 self.log.debug("Exiting application: %s" % self.name)
364 self._exiting = True
365 self._exiting = True
365 sys.exit(exit_status)
366 sys.exit(exit_status)
366
367
367 def attempt(self, func, action='abort'):
368 def attempt(self, func, action='abort'):
368 try:
369 try:
369 func()
370 func()
370 except SystemExit:
371 except SystemExit:
371 raise
372 raise
372 except:
373 except:
373 if action == 'abort':
374 if action == 'abort':
374 self.abort()
375 self.abort()
375 elif action == 'exit':
376 elif action == 'exit':
376 self.exit(0)
377 self.exit(0)
377
378
General Comments 0
You need to be logged in to leave comments. Login now