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