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