##// END OF EJS Templates
dont' swallow sys.exit
Matthias Bussonnier -
Show More
@@ -1,397 +1,397 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 configurables.
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
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 import atexit
16 16 import glob
17 17 import logging
18 18 import os
19 19 import shutil
20 20 import sys
21 21
22 22 from traitlets.config.application import Application, catch_config_error
23 23 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 24 from IPython.core import release, crashhandler
25 25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 26 from IPython.paths import get_ipython_dir, get_ipython_package_dir
27 27 from IPython.utils.path import ensure_dir_exists
28 28 from IPython.utils import py3compat
29 29 from traitlets import List, Unicode, Type, Bool, Dict, Set, Instance, Undefined
30 30
31 31 if os.name == 'nt':
32 32 programdata = os.environ.get('PROGRAMDATA', None)
33 33 if programdata:
34 34 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
35 35 else: # PROGRAMDATA is not defined by default on XP.
36 36 SYSTEM_CONFIG_DIRS = []
37 37 else:
38 38 SYSTEM_CONFIG_DIRS = [
39 39 "/usr/local/etc/ipython",
40 40 "/etc/ipython",
41 41 ]
42 42
43 43
44 44 # aliases and flags
45 45
46 46 base_aliases = {
47 47 'profile-dir' : 'ProfileDir.location',
48 48 'profile' : 'BaseIPythonApplication.profile',
49 49 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
50 50 'log-level' : 'Application.log_level',
51 51 'config' : 'BaseIPythonApplication.extra_config_file',
52 52 }
53 53
54 54 base_flags = dict(
55 55 debug = ({'Application' : {'log_level' : logging.DEBUG}},
56 56 "set log level to logging.DEBUG (maximize logging output)"),
57 57 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
58 58 "set log level to logging.CRITICAL (minimize logging output)"),
59 59 init = ({'BaseIPythonApplication' : {
60 60 'copy_config_files' : True,
61 61 'auto_create' : True}
62 62 }, """Initialize profile with default config files. This is equivalent
63 63 to running `ipython profile create <profile>` prior to startup.
64 64 """)
65 65 )
66 66
67 67 class ProfileAwareConfigLoader(PyFileConfigLoader):
68 68 """A Python file config loader that is aware of IPython profiles."""
69 69 def load_subconfig(self, fname, path=None, profile=None):
70 70 if profile is not None:
71 71 try:
72 72 profile_dir = ProfileDir.find_profile_dir_by_name(
73 73 get_ipython_dir(),
74 74 profile,
75 75 )
76 76 except ProfileDirError:
77 77 return
78 78 path = profile_dir.location
79 79 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
80 80
81 81 class BaseIPythonApplication(Application):
82 82
83 83 name = Unicode(u'ipython')
84 84 description = Unicode(u'IPython: an enhanced interactive Python shell.')
85 85 version = Unicode(release.version)
86 86
87 87 aliases = Dict(base_aliases)
88 88 flags = Dict(base_flags)
89 89 classes = List([ProfileDir])
90 90
91 91 # enable `load_subconfig('cfg.py', profile='name')`
92 92 python_config_loader_class = ProfileAwareConfigLoader
93 93
94 94 # Track whether the config_file has changed,
95 95 # because some logic happens only if we aren't using the default.
96 96 config_file_specified = Set()
97 97
98 98 config_file_name = Unicode()
99 99 def _config_file_name_default(self):
100 100 return self.name.replace('-','_') + u'_config.py'
101 101 def _config_file_name_changed(self, name, old, new):
102 102 if new != old:
103 103 self.config_file_specified.add(new)
104 104
105 105 # The directory that contains IPython's builtin profiles.
106 106 builtin_profile_dir = Unicode(
107 107 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
108 108 )
109 109
110 110 config_file_paths = List(Unicode)
111 111 def _config_file_paths_default(self):
112 112 return [py3compat.getcwd()]
113 113
114 114 extra_config_file = Unicode(config=True,
115 115 help="""Path to an extra config file to load.
116 116
117 117 If specified, load this config file in addition to any other IPython config.
118 118 """)
119 119 def _extra_config_file_changed(self, name, old, new):
120 120 try:
121 121 self.config_files.remove(old)
122 122 except ValueError:
123 123 pass
124 124 self.config_file_specified.add(new)
125 125 self.config_files.append(new)
126 126
127 127 profile = Unicode(u'default', config=True,
128 128 help="""The IPython profile to use."""
129 129 )
130 130
131 131 def _profile_changed(self, name, old, new):
132 132 self.builtin_profile_dir = os.path.join(
133 133 get_ipython_package_dir(), u'config', u'profile', new
134 134 )
135 135
136 136 ipython_dir = Unicode(config=True,
137 137 help="""
138 138 The name of the IPython directory. This directory is used for logging
139 139 configuration (through profiles), history storage, etc. The default
140 140 is usually $HOME/.ipython. This option can also be specified through
141 141 the environment variable IPYTHONDIR.
142 142 """
143 143 )
144 144 def _ipython_dir_default(self):
145 145 d = get_ipython_dir()
146 146 self._ipython_dir_changed('ipython_dir', d, d)
147 147 return d
148 148
149 149 _in_init_profile_dir = False
150 150 profile_dir = Instance(ProfileDir, allow_none=True)
151 151 def _profile_dir_default(self):
152 152 # avoid recursion
153 153 if self._in_init_profile_dir:
154 154 return
155 155 # profile_dir requested early, force initialization
156 156 self.init_profile_dir()
157 157 return self.profile_dir
158 158
159 159 overwrite = Bool(False, config=True,
160 160 help="""Whether to overwrite existing config files when copying""")
161 161 auto_create = Bool(False, config=True,
162 162 help="""Whether to create profile dir if it doesn't exist""")
163 163
164 164 config_files = List(Unicode)
165 165 def _config_files_default(self):
166 166 return [self.config_file_name]
167 167
168 168 copy_config_files = Bool(False, config=True,
169 169 help="""Whether to install the default config files into the profile dir.
170 170 If a new profile is being created, and IPython contains config files for that
171 171 profile, then they will be staged into the new directory. Otherwise,
172 172 default config files will be automatically generated.
173 173 """)
174 174
175 175 verbose_crash = Bool(False, config=True,
176 176 help="""Create a massive crash report when IPython encounters what may be an
177 177 internal error. The default is to append a short message to the
178 178 usual traceback""")
179 179
180 180 # The class to use as the crash handler.
181 181 crash_handler_class = Type(crashhandler.CrashHandler)
182 182
183 183 @catch_config_error
184 184 def __init__(self, **kwargs):
185 185 super(BaseIPythonApplication, self).__init__(**kwargs)
186 186 # ensure current working directory exists
187 187 try:
188 188 directory = py3compat.getcwd()
189 189 except:
190 190 # exit if cwd doesn't exist
191 191 self.log.error("Current working directory doesn't exist.")
192 192 self.exit(1)
193 193
194 194 #-------------------------------------------------------------------------
195 195 # Various stages of Application creation
196 196 #-------------------------------------------------------------------------
197 197
198 198 def init_crash_handler(self):
199 199 """Create a crash handler, typically setting sys.excepthook to it."""
200 200 self.crash_handler = self.crash_handler_class(self)
201 201 sys.excepthook = self.excepthook
202 202 def unset_crashhandler():
203 203 sys.excepthook = sys.__excepthook__
204 204 atexit.register(unset_crashhandler)
205 205
206 206 def excepthook(self, etype, evalue, tb):
207 207 """this is sys.excepthook after init_crashhandler
208 208
209 209 set self.verbose_crash=True to use our full crashhandler, instead of
210 210 a regular traceback with a short message (crash_handler_lite)
211 211 """
212 212
213 213 if self.verbose_crash:
214 214 return self.crash_handler(etype, evalue, tb)
215 215 else:
216 216 return crashhandler.crash_handler_lite(etype, evalue, tb)
217 217
218 218 def _ipython_dir_changed(self, name, old, new):
219 219 if old is not Undefined:
220 220 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
221 221 sys.getfilesystemencoding()
222 222 )
223 223 if str_old in sys.path:
224 224 sys.path.remove(str_old)
225 225 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
226 226 sys.getfilesystemencoding()
227 227 )
228 228 sys.path.append(str_path)
229 229 ensure_dir_exists(new)
230 230 readme = os.path.join(new, 'README')
231 231 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
232 232 if not os.path.exists(readme) and os.path.exists(readme_src):
233 233 shutil.copy(readme_src, readme)
234 234 for d in ('extensions', 'nbextensions'):
235 235 path = os.path.join(new, d)
236 236 try:
237 237 ensure_dir_exists(path)
238 238 except OSError:
239 239 # this will not be EEXIST
240 240 self.log.error("couldn't create path %s: %s", path, e)
241 241 self.log.debug("IPYTHONDIR set to: %s" % new)
242 242
243 243 def load_config_file(self, suppress_errors=True):
244 244 """Load the config file.
245 245
246 246 By default, errors in loading config are handled, and a warning
247 247 printed on screen. For testing, the suppress_errors option is set
248 248 to False, so errors will make tests fail.
249 249 """
250 250 self.log.debug("Searching path %s for config files", self.config_file_paths)
251 251 base_config = 'ipython_config.py'
252 252 self.log.debug("Attempting to load config file: %s" %
253 253 base_config)
254 254 try:
255 255 Application.load_config_file(
256 256 self,
257 257 base_config,
258 258 path=self.config_file_paths
259 259 )
260 260 except ConfigFileNotFound:
261 261 # ignore errors loading parent
262 262 self.log.debug("Config file %s not found", base_config)
263 263 pass
264 264
265 265 for config_file_name in self.config_files:
266 266 if not config_file_name or config_file_name == base_config:
267 267 continue
268 268 self.log.debug("Attempting to load config file: %s" %
269 269 self.config_file_name)
270 270 try:
271 271 Application.load_config_file(
272 272 self,
273 273 config_file_name,
274 274 path=self.config_file_paths
275 275 )
276 276 except ConfigFileNotFound:
277 277 # Only warn if the default config file was NOT being used.
278 278 if config_file_name in self.config_file_specified:
279 279 msg = self.log.warn
280 280 else:
281 281 msg = self.log.debug
282 282 msg("Config file not found, skipping: %s", config_file_name)
283 except:
283 except Exception:
284 284 # For testing purposes.
285 285 if not suppress_errors:
286 286 raise
287 287 self.log.warn("Error loading config file: %s" %
288 288 self.config_file_name, exc_info=True)
289 289
290 290 def init_profile_dir(self):
291 291 """initialize the profile dir"""
292 292 self._in_init_profile_dir = True
293 293 if self.profile_dir is not None:
294 294 # already ran
295 295 return
296 296 if 'ProfileDir.location' not in self.config:
297 297 # location not specified, find by profile name
298 298 try:
299 299 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
300 300 except ProfileDirError:
301 301 # not found, maybe create it (always create default profile)
302 302 if self.auto_create or self.profile == 'default':
303 303 try:
304 304 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
305 305 except ProfileDirError:
306 306 self.log.fatal("Could not create profile: %r"%self.profile)
307 307 self.exit(1)
308 308 else:
309 309 self.log.info("Created profile dir: %r"%p.location)
310 310 else:
311 311 self.log.fatal("Profile %r not found."%self.profile)
312 312 self.exit(1)
313 313 else:
314 314 self.log.debug("Using existing profile dir: %r"%p.location)
315 315 else:
316 316 location = self.config.ProfileDir.location
317 317 # location is fully specified
318 318 try:
319 319 p = ProfileDir.find_profile_dir(location, self.config)
320 320 except ProfileDirError:
321 321 # not found, maybe create it
322 322 if self.auto_create:
323 323 try:
324 324 p = ProfileDir.create_profile_dir(location, self.config)
325 325 except ProfileDirError:
326 326 self.log.fatal("Could not create profile directory: %r"%location)
327 327 self.exit(1)
328 328 else:
329 329 self.log.debug("Creating new profile dir: %r"%location)
330 330 else:
331 331 self.log.fatal("Profile directory %r not found."%location)
332 332 self.exit(1)
333 333 else:
334 334 self.log.info("Using existing profile dir: %r"%location)
335 335 # if profile_dir is specified explicitly, set profile name
336 336 dir_name = os.path.basename(p.location)
337 337 if dir_name.startswith('profile_'):
338 338 self.profile = dir_name[8:]
339 339
340 340 self.profile_dir = p
341 341 self.config_file_paths.append(p.location)
342 342 self._in_init_profile_dir = False
343 343
344 344 def init_config_files(self):
345 345 """[optionally] copy default config files into profile dir."""
346 346 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
347 347 # copy config files
348 348 path = self.builtin_profile_dir
349 349 if self.copy_config_files:
350 350 src = self.profile
351 351
352 352 cfg = self.config_file_name
353 353 if path and os.path.exists(os.path.join(path, cfg)):
354 354 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
355 355 cfg, src, self.profile_dir.location, self.overwrite)
356 356 )
357 357 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
358 358 else:
359 359 self.stage_default_config_file()
360 360 else:
361 361 # Still stage *bundled* config files, but not generated ones
362 362 # This is necessary for `ipython profile=sympy` to load the profile
363 363 # on the first go
364 364 files = glob.glob(os.path.join(path, '*.py'))
365 365 for fullpath in files:
366 366 cfg = os.path.basename(fullpath)
367 367 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
368 368 # file was copied
369 369 self.log.warn("Staging bundled %s from %s into %r"%(
370 370 cfg, self.profile, self.profile_dir.location)
371 371 )
372 372
373 373
374 374 def stage_default_config_file(self):
375 375 """auto generate default config file, and stage it into the profile."""
376 376 s = self.generate_config_file()
377 377 fname = os.path.join(self.profile_dir.location, self.config_file_name)
378 378 if self.overwrite or not os.path.exists(fname):
379 379 self.log.warn("Generating default config file: %r"%(fname))
380 380 with open(fname, 'w') as f:
381 381 f.write(s)
382 382
383 383 @catch_config_error
384 384 def initialize(self, argv=None):
385 385 # don't hook up crash handler before parsing command-line
386 386 self.parse_command_line(argv)
387 387 self.init_crash_handler()
388 388 if self.subapp is not None:
389 389 # stop here if subapp is taking over
390 390 return
391 391 cl_config = self.config
392 392 self.init_profile_dir()
393 393 self.init_config_files()
394 394 self.load_config_file()
395 395 # enforce cl-opts override configfile opts:
396 396 self.update_config(cl_config)
397 397
General Comments 0
You need to be logged in to leave comments. Login now