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