##// END OF EJS Templates
Handle errors from older versions of argparse.
Thomas Kluyver -
Show More
1 NO CONTENT: modified file
@@ -1,459 +1,462 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 configurable objects, 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_version(self, parser):
74 74 """Add the --version option to the parser."""
75 75 parser.add_argument('--version', action="version",
76 76 version=self.version)
77 77
78 78 def _add_arguments(self):
79 79 self._add_ipython_dir(self.parser)
80 80 self._add_log_level(self.parser)
81 try: # Old versions of argparse don't have a version action
81 82 self._add_version(self.parser)
83 except Exception:
84 pass
82 85
83 86
84 87 class Application(object):
85 88 """Load a config, construct configurables and set them running.
86 89
87 90 The configuration of an application can be done via three different Config
88 91 objects, which are loaded and ultimately merged into a single one used
89 92 from that point on by the app. These are:
90 93
91 94 1. default_config: internal defaults, implemented in code.
92 95 2. file_config: read from the filesystem.
93 96 3. command_line_config: read from the system's command line flags.
94 97
95 98 During initialization, 3 is actually read before 2, since at the
96 99 command-line one may override the location of the file to be read. But the
97 100 above is the order in which the merge is made.
98 101 """
99 102
100 103 name = u'ipython'
101 104 description = 'IPython: an enhanced interactive Python shell.'
102 105 #: Usage message printed by argparse. If None, auto-generate
103 106 usage = None
104 107 #: The command line config loader. Subclass of ArgParseConfigLoader.
105 108 command_line_loader = BaseAppConfigLoader
106 109 #: The name of the config file to load, determined at runtime
107 110 config_file_name = None
108 111 #: The name of the default config file. Track separately from the actual
109 112 #: name because some logic happens only if we aren't using the default.
110 113 default_config_file_name = u'ipython_config.py'
111 114 default_log_level = logging.WARN
112 115 #: Set by --profile option
113 116 profile_name = None
114 117 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
115 118 ipython_dir = None
116 119 #: Internal defaults, implemented in code.
117 120 default_config = None
118 121 #: Read from the filesystem.
119 122 file_config = None
120 123 #: Read from the system's command line flags.
121 124 command_line_config = None
122 125 #: The final config that will be passed to the main object.
123 126 master_config = None
124 127 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
125 128 argv = None
126 129 #: extra arguments computed by the command-line loader
127 130 extra_args = None
128 131 #: The class to use as the crash handler.
129 132 crash_handler_class = crashhandler.CrashHandler
130 133
131 134 # Private attributes
132 135 _exiting = False
133 136 _initialized = False
134 137
135 138 def __init__(self, argv=None):
136 139 self.argv = sys.argv[1:] if argv is None else argv
137 140 self.init_logger()
138 141
139 142 def init_logger(self):
140 143 self.log = logging.getLogger(self.__class__.__name__)
141 144 # This is used as the default until the command line arguments are read.
142 145 self.log.setLevel(self.default_log_level)
143 146 self._log_handler = logging.StreamHandler()
144 147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 148 self._log_handler.setFormatter(self._log_formatter)
146 149 self.log.addHandler(self._log_handler)
147 150
148 151 def _set_log_level(self, level):
149 152 self.log.setLevel(level)
150 153
151 154 def _get_log_level(self):
152 155 return self.log.level
153 156
154 157 log_level = property(_get_log_level, _set_log_level)
155 158
156 159 def initialize(self):
157 160 """Initialize the application.
158 161
159 162 Loads all configuration information and sets all application state, but
160 163 does not start any relevant processing (typically some kind of event
161 164 loop).
162 165
163 166 Once this method has been called, the application is flagged as
164 167 initialized and the method becomes a no-op."""
165 168
166 169 if self._initialized:
167 170 return
168 171
169 172 # The first part is protected with an 'attempt' wrapper, that will log
170 173 # failures with the basic system traceback machinery. Once our crash
171 174 # handler is in place, we can let any subsequent exception propagate,
172 175 # as our handler will log it with much better detail than the default.
173 176 self.attempt(self.create_crash_handler)
174 177
175 178 # Configuration phase
176 179 # Default config (internally hardwired in application code)
177 180 self.create_default_config()
178 181 self.log_default_config()
179 182 self.set_default_config_log_level()
180 183
181 184 # Command-line config
182 185 self.pre_load_command_line_config()
183 186 self.load_command_line_config()
184 187 self.set_command_line_config_log_level()
185 188 self.post_load_command_line_config()
186 189 self.log_command_line_config()
187 190
188 191 # Find resources needed for filesystem access, using information from
189 192 # the above two
190 193 self.find_ipython_dir()
191 194 self.find_resources()
192 195 self.find_config_file_name()
193 196 self.find_config_file_paths()
194 197
195 198 # File-based config
196 199 self.pre_load_file_config()
197 200 self.load_file_config()
198 201 self.set_file_config_log_level()
199 202 self.post_load_file_config()
200 203 self.log_file_config()
201 204
202 205 # Merge all config objects into a single one the app can then use
203 206 self.merge_configs()
204 207 self.log_master_config()
205 208
206 209 # Construction phase
207 210 self.pre_construct()
208 211 self.construct()
209 212 self.post_construct()
210 213
211 214 # Done, flag as such and
212 215 self._initialized = True
213 216
214 217 def start(self):
215 218 """Start the application."""
216 219 self.initialize()
217 220 self.start_app()
218 221
219 222 #-------------------------------------------------------------------------
220 223 # Various stages of Application creation
221 224 #-------------------------------------------------------------------------
222 225
223 226 def create_crash_handler(self):
224 227 """Create a crash handler, typically setting sys.excepthook to it."""
225 228 self.crash_handler = self.crash_handler_class(self)
226 229 sys.excepthook = self.crash_handler
227 230
228 231 def create_default_config(self):
229 232 """Create defaults that can't be set elsewhere.
230 233
231 234 For the most part, we try to set default in the class attributes
232 235 of Configurables. But, defaults the top-level Application (which is
233 236 not a HasTraits or Configurables) are not set in this way. Instead
234 237 we set them here. The Global section is for variables like this that
235 238 don't belong to a particular configurable.
236 239 """
237 240 c = Config()
238 241 c.Global.ipython_dir = get_ipython_dir()
239 242 c.Global.log_level = self.log_level
240 243 self.default_config = c
241 244
242 245 def log_default_config(self):
243 246 self.log.debug('Default config loaded:')
244 247 self.log.debug(repr(self.default_config))
245 248
246 249 def set_default_config_log_level(self):
247 250 try:
248 251 self.log_level = self.default_config.Global.log_level
249 252 except AttributeError:
250 253 # Fallback to the default_log_level class attribute
251 254 pass
252 255
253 256 def create_command_line_config(self):
254 257 """Create and return a command line config loader."""
255 258 return self.command_line_loader(
256 259 self.argv,
257 260 description=self.description,
258 261 version=release.version,
259 262 usage=self.usage
260 263 )
261 264
262 265 def pre_load_command_line_config(self):
263 266 """Do actions just before loading the command line config."""
264 267 pass
265 268
266 269 def load_command_line_config(self):
267 270 """Load the command line config."""
268 271 loader = self.create_command_line_config()
269 272 self.command_line_config = loader.load_config()
270 273 self.extra_args = loader.get_extra_args()
271 274
272 275 def set_command_line_config_log_level(self):
273 276 try:
274 277 self.log_level = self.command_line_config.Global.log_level
275 278 except AttributeError:
276 279 pass
277 280
278 281 def post_load_command_line_config(self):
279 282 """Do actions just after loading the command line config."""
280 283 pass
281 284
282 285 def log_command_line_config(self):
283 286 self.log.debug("Command line config loaded:")
284 287 self.log.debug(repr(self.command_line_config))
285 288
286 289 def find_ipython_dir(self):
287 290 """Set the IPython directory.
288 291
289 292 This sets ``self.ipython_dir``, but the actual value that is passed to
290 293 the application is kept in either ``self.default_config`` or
291 294 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
292 295 ``sys.path`` so config files there can be referenced by other config
293 296 files.
294 297 """
295 298
296 299 try:
297 300 self.ipython_dir = self.command_line_config.Global.ipython_dir
298 301 except AttributeError:
299 302 self.ipython_dir = self.default_config.Global.ipython_dir
300 303 sys.path.append(os.path.abspath(self.ipython_dir))
301 304 if not os.path.isdir(self.ipython_dir):
302 305 os.makedirs(self.ipython_dir, mode=0777)
303 306 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
304 307
305 308 def find_resources(self):
306 309 """Find other resources that need to be in place.
307 310
308 311 Things like cluster directories need to be in place to find the
309 312 config file. These happen right after the IPython directory has
310 313 been set.
311 314 """
312 315 pass
313 316
314 317 def find_config_file_name(self):
315 318 """Find the config file name for this application.
316 319
317 320 This must set ``self.config_file_name`` to the filename of the
318 321 config file to use (just the filename). The search paths for the
319 322 config file are set in :meth:`find_config_file_paths` and then passed
320 323 to the config file loader where they are resolved to an absolute path.
321 324
322 325 If a profile has been set at the command line, this will resolve it.
323 326 """
324 327 try:
325 328 self.config_file_name = self.command_line_config.Global.config_file
326 329 except AttributeError:
327 330 pass
328 331 else:
329 332 return
330 333
331 334 try:
332 335 self.profile_name = self.command_line_config.Global.profile
333 336 except AttributeError:
334 337 # Just use the default as there is no profile
335 338 self.config_file_name = self.default_config_file_name
336 339 else:
337 340 # Use the default config file name and profile name if set
338 341 # to determine the used config file name.
339 342 name_parts = self.default_config_file_name.split('.')
340 343 name_parts.insert(1, u'_' + self.profile_name + u'.')
341 344 self.config_file_name = ''.join(name_parts)
342 345
343 346 def find_config_file_paths(self):
344 347 """Set the search paths for resolving the config file.
345 348
346 349 This must set ``self.config_file_paths`` to a sequence of search
347 350 paths to pass to the config file loader.
348 351 """
349 352 # Include our own profiles directory last, so that users can still find
350 353 # our shipped copies of builtin profiles even if they don't have them
351 354 # in their local ipython directory.
352 355 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
353 356 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
354 357
355 358 def pre_load_file_config(self):
356 359 """Do actions before the config file is loaded."""
357 360 pass
358 361
359 362 def load_file_config(self):
360 363 """Load the config file.
361 364
362 365 This tries to load the config file from disk. If successful, the
363 366 ``CONFIG_FILE`` config variable is set to the resolved config file
364 367 location. If not successful, an empty config is used.
365 368 """
366 369 self.log.debug("Attempting to load config file: %s" %
367 370 self.config_file_name)
368 371 loader = PyFileConfigLoader(self.config_file_name,
369 372 path=self.config_file_paths)
370 373 try:
371 374 self.file_config = loader.load_config()
372 375 self.file_config.Global.config_file = loader.full_filename
373 376 except IOError:
374 377 # Only warn if the default config file was NOT being used.
375 378 if not self.config_file_name==self.default_config_file_name:
376 379 self.log.warn("Config file not found, skipping: %s" %
377 380 self.config_file_name, exc_info=True)
378 381 self.file_config = Config()
379 382 except:
380 383 self.log.warn("Error loading config file: %s" %
381 384 self.config_file_name, exc_info=True)
382 385 self.file_config = Config()
383 386
384 387 def set_file_config_log_level(self):
385 388 # We need to keeep self.log_level updated. But we only use the value
386 389 # of the file_config if a value was not specified at the command
387 390 # line, because the command line overrides everything.
388 391 if not hasattr(self.command_line_config.Global, 'log_level'):
389 392 try:
390 393 self.log_level = self.file_config.Global.log_level
391 394 except AttributeError:
392 395 pass # Use existing value
393 396
394 397 def post_load_file_config(self):
395 398 """Do actions after the config file is loaded."""
396 399 pass
397 400
398 401 def log_file_config(self):
399 402 if hasattr(self.file_config.Global, 'config_file'):
400 403 self.log.debug("Config file loaded: %s" %
401 404 self.file_config.Global.config_file)
402 405 self.log.debug(repr(self.file_config))
403 406
404 407 def merge_configs(self):
405 408 """Merge the default, command line and file config objects."""
406 409 config = Config()
407 410 config._merge(self.default_config)
408 411 config._merge(self.file_config)
409 412 config._merge(self.command_line_config)
410 413
411 414 # XXX fperez - propose to Brian we rename master_config to simply
412 415 # config, I think this is going to be heavily used in examples and
413 416 # application code and the name is shorter/easier to find/remember.
414 417 # For now, just alias it...
415 418 self.master_config = config
416 419 self.config = config
417 420
418 421 def log_master_config(self):
419 422 self.log.debug("Master config created:")
420 423 self.log.debug(repr(self.master_config))
421 424
422 425 def pre_construct(self):
423 426 """Do actions after the config has been built, but before construct."""
424 427 pass
425 428
426 429 def construct(self):
427 430 """Construct the main objects that make up this app."""
428 431 self.log.debug("Constructing main objects for application")
429 432
430 433 def post_construct(self):
431 434 """Do actions after construct, but before starting the app."""
432 435 pass
433 436
434 437 def start_app(self):
435 438 """Actually start the app."""
436 439 self.log.debug("Starting application")
437 440
438 441 #-------------------------------------------------------------------------
439 442 # Utility methods
440 443 #-------------------------------------------------------------------------
441 444
442 445 def exit(self, exit_status=0):
443 446 if self._exiting:
444 447 pass
445 448 else:
446 449 self.log.debug("Exiting application: %s" % self.name)
447 450 self._exiting = True
448 451 sys.exit(exit_status)
449 452
450 453 def attempt(self, func):
451 454 try:
452 455 func()
453 456 except SystemExit:
454 457 raise
455 458 except:
456 459 self.log.critical("Aborting application: %s" % self.name,
457 460 exc_info=True)
458 461 self.exit(0)
459 462
General Comments 0
You need to be logged in to leave comments. Login now