##// END OF EJS Templates
Testing '#:' attribute docstrings for sphinx....
Fernando Perez -
Show More
@@ -1,378 +1,378
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 # User's ipython directory, typically ~/.ipython/
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 158 c = Config()
159 159 c.Global.ipython_dir = get_ipython_dir()
160 160 c.Global.log_level = self.log_level
161 161 self.default_config = c
162 162
163 163 def log_default_config(self):
164 164 self.log.debug('Default config loaded:')
165 165 self.log.debug(repr(self.default_config))
166 166
167 167 def set_default_config_log_level(self):
168 168 try:
169 169 self.log_level = self.default_config.Global.log_level
170 170 except AttributeError:
171 171 # Fallback to the default_log_level class attribute
172 172 pass
173 173
174 174 def create_command_line_config(self):
175 175 """Create and return a command line config loader."""
176 176 return BaseAppArgParseConfigLoader(
177 177 description=self.description,
178 178 version=release.version
179 179 )
180 180
181 181 def pre_load_command_line_config(self):
182 182 """Do actions just before loading the command line config."""
183 183 pass
184 184
185 185 def load_command_line_config(self):
186 186 """Load the command line config."""
187 187 loader = self.create_command_line_config()
188 188 self.command_line_config = loader.load_config()
189 189 self.extra_args = loader.get_extra_args()
190 190
191 191 def set_command_line_config_log_level(self):
192 192 try:
193 193 self.log_level = self.command_line_config.Global.log_level
194 194 except AttributeError:
195 195 pass
196 196
197 197 def post_load_command_line_config(self):
198 198 """Do actions just after loading the command line config."""
199 199 pass
200 200
201 201 def log_command_line_config(self):
202 202 self.log.debug("Command line config loaded:")
203 203 self.log.debug(repr(self.command_line_config))
204 204
205 205 def find_ipython_dir(self):
206 206 """Set the IPython directory.
207 207
208 208 This sets ``self.ipython_dir``, but the actual value that is passed to
209 209 the application is kept in either ``self.default_config`` or
210 210 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
211 211 ``sys.path`` so config files there can be referenced by other config
212 212 files.
213 213 """
214 214
215 215 try:
216 216 self.ipython_dir = self.command_line_config.Global.ipython_dir
217 217 except AttributeError:
218 218 self.ipython_dir = self.default_config.Global.ipython_dir
219 219 sys.path.append(os.path.abspath(self.ipython_dir))
220 220 if not os.path.isdir(self.ipython_dir):
221 221 os.makedirs(self.ipython_dir, mode=0777)
222 222 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
223 223
224 224 def find_resources(self):
225 225 """Find other resources that need to be in place.
226 226
227 227 Things like cluster directories need to be in place to find the
228 228 config file. These happen right after the IPython directory has
229 229 been set.
230 230 """
231 231 pass
232 232
233 233 def find_config_file_name(self):
234 234 """Find the config file name for this application.
235 235
236 236 This must set ``self.config_file_name`` to the filename of the
237 237 config file to use (just the filename). The search paths for the
238 238 config file are set in :meth:`find_config_file_paths` and then passed
239 239 to the config file loader where they are resolved to an absolute path.
240 240
241 241 If a profile has been set at the command line, this will resolve it.
242 242 """
243 243
244 244 try:
245 245 self.config_file_name = self.command_line_config.Global.config_file
246 246 except AttributeError:
247 247 pass
248 248
249 249 try:
250 250 self.profile_name = self.command_line_config.Global.profile
251 251 except AttributeError:
252 252 pass
253 253 else:
254 254 name_parts = self.config_file_name.split('.')
255 255 name_parts.insert(1, u'_' + self.profile_name + u'.')
256 256 self.config_file_name = ''.join(name_parts)
257 257
258 258 def find_config_file_paths(self):
259 259 """Set the search paths for resolving the config file.
260 260
261 261 This must set ``self.config_file_paths`` to a sequence of search
262 262 paths to pass to the config file loader.
263 263 """
264 264 # Include our own profiles directory last, so that users can still find
265 265 # our shipped copies of builtin profiles even if they don't have them
266 266 # in their local ipython directory.
267 267 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
268 268 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
269 269
270 270 def pre_load_file_config(self):
271 271 """Do actions before the config file is loaded."""
272 272 pass
273 273
274 274 def load_file_config(self):
275 275 """Load the config file.
276 276
277 277 This tries to load the config file from disk. If successful, the
278 278 ``CONFIG_FILE`` config variable is set to the resolved config file
279 279 location. If not successful, an empty config is used.
280 280 """
281 281 self.log.debug("Attempting to load config file: %s" %
282 282 self.config_file_name)
283 283 loader = PyFileConfigLoader(self.config_file_name,
284 284 path=self.config_file_paths)
285 285 try:
286 286 self.file_config = loader.load_config()
287 287 self.file_config.Global.config_file = loader.full_filename
288 288 except IOError:
289 289 # Only warn if the default config file was NOT being used.
290 290 if not self.config_file_name==self.default_config_file_name:
291 291 self.log.warn("Config file not found, skipping: %s" %
292 292 self.config_file_name, exc_info=True)
293 293 self.file_config = Config()
294 294 except:
295 295 self.log.warn("Error loading config file: %s" %
296 296 self.config_file_name, exc_info=True)
297 297 self.file_config = Config()
298 298
299 299 def set_file_config_log_level(self):
300 300 # We need to keeep self.log_level updated. But we only use the value
301 301 # of the file_config if a value was not specified at the command
302 302 # line, because the command line overrides everything.
303 303 if not hasattr(self.command_line_config.Global, 'log_level'):
304 304 try:
305 305 self.log_level = self.file_config.Global.log_level
306 306 except AttributeError:
307 307 pass # Use existing value
308 308
309 309 def post_load_file_config(self):
310 310 """Do actions after the config file is loaded."""
311 311 pass
312 312
313 313 def log_file_config(self):
314 314 if hasattr(self.file_config.Global, 'config_file'):
315 315 self.log.debug("Config file loaded: %s" %
316 316 self.file_config.Global.config_file)
317 317 self.log.debug(repr(self.file_config))
318 318
319 319 def merge_configs(self):
320 320 """Merge the default, command line and file config objects."""
321 321 config = Config()
322 322 config._merge(self.default_config)
323 323 config._merge(self.file_config)
324 324 config._merge(self.command_line_config)
325 325 self.master_config = config
326 326
327 327 def log_master_config(self):
328 328 self.log.debug("Master config created:")
329 329 self.log.debug(repr(self.master_config))
330 330
331 331 def pre_construct(self):
332 332 """Do actions after the config has been built, but before construct."""
333 333 pass
334 334
335 335 def construct(self):
336 336 """Construct the main components that make up this app."""
337 337 self.log.debug("Constructing components for application")
338 338
339 339 def post_construct(self):
340 340 """Do actions after construct, but before starting the app."""
341 341 pass
342 342
343 343 def start_app(self):
344 344 """Actually start the app."""
345 345 self.log.debug("Starting application")
346 346
347 347 #-------------------------------------------------------------------------
348 348 # Utility methods
349 349 #-------------------------------------------------------------------------
350 350
351 351 def abort(self):
352 352 """Abort the starting of the application."""
353 353 if self._exiting:
354 354 pass
355 355 else:
356 356 self.log.critical("Aborting application: %s" % self.name, exc_info=True)
357 357 self._exiting = True
358 358 sys.exit(1)
359 359
360 360 def exit(self, exit_status=0):
361 361 if self._exiting:
362 362 pass
363 363 else:
364 364 self.log.debug("Exiting application: %s" % self.name)
365 365 self._exiting = True
366 366 sys.exit(exit_status)
367 367
368 368 def attempt(self, func, action='abort'):
369 369 try:
370 370 func()
371 371 except SystemExit:
372 372 raise
373 373 except:
374 374 if action == 'abort':
375 375 self.abort()
376 376 elif action == 'exit':
377 377 self.exit(0)
378 378
General Comments 0
You need to be logged in to leave comments. Login now