##// END OF EJS Templates
Catch keyboard interrupt and quit
Matthias Bussonnier -
Show More
@@ -1,407 +1,410 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 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 initialize_subcommand(self, subc, argv=None):
199 199 if subc in self.deprecated_subcommands:
200 200 import time
201 201 self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed "
202 202 "in future versions.".format(sub=subc))
203 203 self.log.warning("You likely want to use `jupyter {sub}`... continue "
204 "in 5 sec".format(sub=subc))
205 time.sleep(5)
204 "in 5 sec. Press Ctrl-C to quit now.".format(sub=subc))
205 try:
206 time.sleep(5)
207 except KeyboardInterrupt:
208 sys.exit(1)
206 209 return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv)
207 210
208 211 def init_crash_handler(self):
209 212 """Create a crash handler, typically setting sys.excepthook to it."""
210 213 self.crash_handler = self.crash_handler_class(self)
211 214 sys.excepthook = self.excepthook
212 215 def unset_crashhandler():
213 216 sys.excepthook = sys.__excepthook__
214 217 atexit.register(unset_crashhandler)
215 218
216 219 def excepthook(self, etype, evalue, tb):
217 220 """this is sys.excepthook after init_crashhandler
218 221
219 222 set self.verbose_crash=True to use our full crashhandler, instead of
220 223 a regular traceback with a short message (crash_handler_lite)
221 224 """
222 225
223 226 if self.verbose_crash:
224 227 return self.crash_handler(etype, evalue, tb)
225 228 else:
226 229 return crashhandler.crash_handler_lite(etype, evalue, tb)
227 230
228 231 def _ipython_dir_changed(self, name, old, new):
229 232 if old is not Undefined:
230 233 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
231 234 sys.getfilesystemencoding()
232 235 )
233 236 if str_old in sys.path:
234 237 sys.path.remove(str_old)
235 238 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
236 239 sys.getfilesystemencoding()
237 240 )
238 241 sys.path.append(str_path)
239 242 ensure_dir_exists(new)
240 243 readme = os.path.join(new, 'README')
241 244 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
242 245 if not os.path.exists(readme) and os.path.exists(readme_src):
243 246 shutil.copy(readme_src, readme)
244 247 for d in ('extensions', 'nbextensions'):
245 248 path = os.path.join(new, d)
246 249 try:
247 250 ensure_dir_exists(path)
248 251 except OSError as e:
249 252 # this will not be EEXIST
250 253 self.log.error("couldn't create path %s: %s", path, e)
251 254 self.log.debug("IPYTHONDIR set to: %s" % new)
252 255
253 256 def load_config_file(self, suppress_errors=True):
254 257 """Load the config file.
255 258
256 259 By default, errors in loading config are handled, and a warning
257 260 printed on screen. For testing, the suppress_errors option is set
258 261 to False, so errors will make tests fail.
259 262 """
260 263 self.log.debug("Searching path %s for config files", self.config_file_paths)
261 264 base_config = 'ipython_config.py'
262 265 self.log.debug("Attempting to load config file: %s" %
263 266 base_config)
264 267 try:
265 268 Application.load_config_file(
266 269 self,
267 270 base_config,
268 271 path=self.config_file_paths
269 272 )
270 273 except ConfigFileNotFound:
271 274 # ignore errors loading parent
272 275 self.log.debug("Config file %s not found", base_config)
273 276 pass
274 277
275 278 for config_file_name in self.config_files:
276 279 if not config_file_name or config_file_name == base_config:
277 280 continue
278 281 self.log.debug("Attempting to load config file: %s" %
279 282 self.config_file_name)
280 283 try:
281 284 Application.load_config_file(
282 285 self,
283 286 config_file_name,
284 287 path=self.config_file_paths
285 288 )
286 289 except ConfigFileNotFound:
287 290 # Only warn if the default config file was NOT being used.
288 291 if config_file_name in self.config_file_specified:
289 292 msg = self.log.warn
290 293 else:
291 294 msg = self.log.debug
292 295 msg("Config file not found, skipping: %s", config_file_name)
293 296 except Exception:
294 297 # For testing purposes.
295 298 if not suppress_errors:
296 299 raise
297 300 self.log.warn("Error loading config file: %s" %
298 301 self.config_file_name, exc_info=True)
299 302
300 303 def init_profile_dir(self):
301 304 """initialize the profile dir"""
302 305 self._in_init_profile_dir = True
303 306 if self.profile_dir is not None:
304 307 # already ran
305 308 return
306 309 if 'ProfileDir.location' not in self.config:
307 310 # location not specified, find by profile name
308 311 try:
309 312 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
310 313 except ProfileDirError:
311 314 # not found, maybe create it (always create default profile)
312 315 if self.auto_create or self.profile == 'default':
313 316 try:
314 317 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
315 318 except ProfileDirError:
316 319 self.log.fatal("Could not create profile: %r"%self.profile)
317 320 self.exit(1)
318 321 else:
319 322 self.log.info("Created profile dir: %r"%p.location)
320 323 else:
321 324 self.log.fatal("Profile %r not found."%self.profile)
322 325 self.exit(1)
323 326 else:
324 327 self.log.debug("Using existing profile dir: %r"%p.location)
325 328 else:
326 329 location = self.config.ProfileDir.location
327 330 # location is fully specified
328 331 try:
329 332 p = ProfileDir.find_profile_dir(location, self.config)
330 333 except ProfileDirError:
331 334 # not found, maybe create it
332 335 if self.auto_create:
333 336 try:
334 337 p = ProfileDir.create_profile_dir(location, self.config)
335 338 except ProfileDirError:
336 339 self.log.fatal("Could not create profile directory: %r"%location)
337 340 self.exit(1)
338 341 else:
339 342 self.log.debug("Creating new profile dir: %r"%location)
340 343 else:
341 344 self.log.fatal("Profile directory %r not found."%location)
342 345 self.exit(1)
343 346 else:
344 347 self.log.info("Using existing profile dir: %r"%location)
345 348 # if profile_dir is specified explicitly, set profile name
346 349 dir_name = os.path.basename(p.location)
347 350 if dir_name.startswith('profile_'):
348 351 self.profile = dir_name[8:]
349 352
350 353 self.profile_dir = p
351 354 self.config_file_paths.append(p.location)
352 355 self._in_init_profile_dir = False
353 356
354 357 def init_config_files(self):
355 358 """[optionally] copy default config files into profile dir."""
356 359 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
357 360 # copy config files
358 361 path = self.builtin_profile_dir
359 362 if self.copy_config_files:
360 363 src = self.profile
361 364
362 365 cfg = self.config_file_name
363 366 if path and os.path.exists(os.path.join(path, cfg)):
364 367 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
365 368 cfg, src, self.profile_dir.location, self.overwrite)
366 369 )
367 370 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
368 371 else:
369 372 self.stage_default_config_file()
370 373 else:
371 374 # Still stage *bundled* config files, but not generated ones
372 375 # This is necessary for `ipython profile=sympy` to load the profile
373 376 # on the first go
374 377 files = glob.glob(os.path.join(path, '*.py'))
375 378 for fullpath in files:
376 379 cfg = os.path.basename(fullpath)
377 380 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
378 381 # file was copied
379 382 self.log.warn("Staging bundled %s from %s into %r"%(
380 383 cfg, self.profile, self.profile_dir.location)
381 384 )
382 385
383 386
384 387 def stage_default_config_file(self):
385 388 """auto generate default config file, and stage it into the profile."""
386 389 s = self.generate_config_file()
387 390 fname = os.path.join(self.profile_dir.location, self.config_file_name)
388 391 if self.overwrite or not os.path.exists(fname):
389 392 self.log.warn("Generating default config file: %r"%(fname))
390 393 with open(fname, 'w') as f:
391 394 f.write(s)
392 395
393 396 @catch_config_error
394 397 def initialize(self, argv=None):
395 398 # don't hook up crash handler before parsing command-line
396 399 self.parse_command_line(argv)
397 400 self.init_crash_handler()
398 401 if self.subapp is not None:
399 402 # stop here if subapp is taking over
400 403 return
401 404 cl_config = self.config
402 405 self.init_profile_dir()
403 406 self.init_config_files()
404 407 self.load_config_file()
405 408 # enforce cl-opts override configfile opts:
406 409 self.update_config(cl_config)
407 410
General Comments 0
You need to be logged in to leave comments. Login now