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