Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/core/application.py: 34%

252 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1# encoding: utf-8 

2""" 

3An application for IPython. 

4 

5All top-level applications should use the classes in this module for 

6handling configuration and creating configurables. 

7 

8The job of an :class:`Application` is to create the master configuration 

9object and then create the configurable objects, passing the config to them. 

10""" 

11 

12# Copyright (c) IPython Development Team. 

13# Distributed under the terms of the Modified BSD License. 

14 

15import atexit 

16from copy import deepcopy 

17import logging 

18import os 

19import shutil 

20import sys 

21 

22from pathlib import Path 

23 

24from traitlets.config.application import Application, catch_config_error 

25from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader 

26from IPython.core import release, crashhandler 

27from IPython.core.profiledir import ProfileDir, ProfileDirError 

28from IPython.paths import get_ipython_dir, get_ipython_package_dir 

29from IPython.utils.path import ensure_dir_exists 

30from traitlets import ( 

31 List, Unicode, Type, Bool, Set, Instance, Undefined, 

32 default, observe, 

33) 

34 

35if os.name == "nt": 

36 programdata = os.environ.get("PROGRAMDATA", None) 

37 if programdata is not None: 

38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] 

39 else: # PROGRAMDATA is not defined by default on XP. 

40 SYSTEM_CONFIG_DIRS = [] 

41else: 

42 SYSTEM_CONFIG_DIRS = [ 

43 "/usr/local/etc/ipython", 

44 "/etc/ipython", 

45 ] 

46 

47 

48ENV_CONFIG_DIRS = [] 

49_env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') 

50if _env_config_dir not in SYSTEM_CONFIG_DIRS: 

51 # only add ENV_CONFIG if sys.prefix is not already included 

52 ENV_CONFIG_DIRS.append(_env_config_dir) 

53 

54 

55_envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') 

56if _envvar in {None, ''}: 

57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None 

58else: 

59 if _envvar.lower() in {'1','true'}: 

60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True 

61 elif _envvar.lower() in {'0','false'} : 

62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False 

63 else: 

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 

66# aliases and flags 

67 

68base_aliases = {} 

69if isinstance(Application.aliases, dict): 

70 # traitlets 5 

71 base_aliases.update(Application.aliases) 

72base_aliases.update( 

73 { 

74 "profile-dir": "ProfileDir.location", 

75 "profile": "BaseIPythonApplication.profile", 

76 "ipython-dir": "BaseIPythonApplication.ipython_dir", 

77 "log-level": "Application.log_level", 

78 "config": "BaseIPythonApplication.extra_config_file", 

79 } 

80) 

81 

82base_flags = dict() 

83if isinstance(Application.flags, dict): 

84 # traitlets 5 

85 base_flags.update(Application.flags) 

86base_flags.update( 

87 dict( 

88 debug=( 

89 {"Application": {"log_level": logging.DEBUG}}, 

90 "set log level to logging.DEBUG (maximize logging output)", 

91 ), 

92 quiet=( 

93 {"Application": {"log_level": logging.CRITICAL}}, 

94 "set log level to logging.CRITICAL (minimize logging output)", 

95 ), 

96 init=( 

97 { 

98 "BaseIPythonApplication": { 

99 "copy_config_files": True, 

100 "auto_create": True, 

101 } 

102 }, 

103 """Initialize profile with default config files. This is equivalent 

104 to running `ipython profile create <profile>` prior to startup. 

105 """, 

106 ), 

107 ) 

108) 

109 

110 

111class ProfileAwareConfigLoader(PyFileConfigLoader): 

112 """A Python file config loader that is aware of IPython profiles.""" 

113 def load_subconfig(self, fname, path=None, profile=None): 

114 if profile is not None: 

115 try: 

116 profile_dir = ProfileDir.find_profile_dir_by_name( 

117 get_ipython_dir(), 

118 profile, 

119 ) 

120 except ProfileDirError: 

121 return 

122 path = profile_dir.location 

123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) 

124 

125class BaseIPythonApplication(Application): 

126 name = "ipython" 

127 description = "IPython: an enhanced interactive Python shell." 

128 version = Unicode(release.version) 

129 

130 aliases = base_aliases 

131 flags = base_flags 

132 classes = List([ProfileDir]) 

133 

134 # enable `load_subconfig('cfg.py', profile='name')` 

135 python_config_loader_class = ProfileAwareConfigLoader 

136 

137 # Track whether the config_file has changed, 

138 # because some logic happens only if we aren't using the default. 

139 config_file_specified = Set() 

140 

141 config_file_name = Unicode() 

142 @default('config_file_name') 

143 def _config_file_name_default(self): 

144 return self.name.replace('-','_') + u'_config.py' 

145 @observe('config_file_name') 

146 def _config_file_name_changed(self, change): 

147 if change['new'] != change['old']: 

148 self.config_file_specified.add(change['new']) 

149 

150 # The directory that contains IPython's builtin profiles. 

151 builtin_profile_dir = Unicode( 

152 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') 

153 ) 

154 

155 config_file_paths = List(Unicode()) 

156 @default('config_file_paths') 

157 def _config_file_paths_default(self): 

158 return [] 

159 

160 extra_config_file = Unicode( 

161 help="""Path to an extra config file to load. 

162  

163 If specified, load this config file in addition to any other IPython config. 

164 """).tag(config=True) 

165 @observe('extra_config_file') 

166 def _extra_config_file_changed(self, change): 

167 old = change['old'] 

168 new = change['new'] 

169 try: 

170 self.config_files.remove(old) 

171 except ValueError: 

172 pass 

173 self.config_file_specified.add(new) 

174 self.config_files.append(new) 

175 

176 profile = Unicode(u'default', 

177 help="""The IPython profile to use.""" 

178 ).tag(config=True) 

179 

180 @observe('profile') 

181 def _profile_changed(self, change): 

182 self.builtin_profile_dir = os.path.join( 

183 get_ipython_package_dir(), u'config', u'profile', change['new'] 

184 ) 

185 

186 add_ipython_dir_to_sys_path = Bool( 

187 False, 

188 """Should the IPython profile directory be added to sys path ? 

189 

190 This option was non-existing before IPython 8.0, and ipython_dir was added to 

191 sys path to allow import of extensions present there. This was historical 

192 baggage from when pip did not exist. This now default to false, 

193 but can be set to true for legacy reasons. 

194 """, 

195 ).tag(config=True) 

196 

197 ipython_dir = Unicode( 

198 help=""" 

199 The name of the IPython directory. This directory is used for logging 

200 configuration (through profiles), history storage, etc. The default 

201 is usually $HOME/.ipython. This option can also be specified through 

202 the environment variable IPYTHONDIR. 

203 """ 

204 ).tag(config=True) 

205 @default('ipython_dir') 

206 def _ipython_dir_default(self): 

207 d = get_ipython_dir() 

208 self._ipython_dir_changed({ 

209 'name': 'ipython_dir', 

210 'old': d, 

211 'new': d, 

212 }) 

213 return d 

214 

215 _in_init_profile_dir = False 

216 profile_dir = Instance(ProfileDir, allow_none=True) 

217 @default('profile_dir') 

218 def _profile_dir_default(self): 

219 # avoid recursion 

220 if self._in_init_profile_dir: 

221 return 

222 # profile_dir requested early, force initialization 

223 self.init_profile_dir() 

224 return self.profile_dir 

225 

226 overwrite = Bool(False, 

227 help="""Whether to overwrite existing config files when copying""" 

228 ).tag(config=True) 

229 auto_create = Bool(False, 

230 help="""Whether to create profile dir if it doesn't exist""" 

231 ).tag(config=True) 

232 

233 config_files = List(Unicode()) 

234 @default('config_files') 

235 def _config_files_default(self): 

236 return [self.config_file_name] 

237 

238 copy_config_files = Bool(False, 

239 help="""Whether to install the default config files into the profile dir. 

240 If a new profile is being created, and IPython contains config files for that 

241 profile, then they will be staged into the new directory. Otherwise, 

242 default config files will be automatically generated. 

243 """).tag(config=True) 

244 

245 verbose_crash = Bool(False, 

246 help="""Create a massive crash report when IPython encounters what may be an 

247 internal error. The default is to append a short message to the 

248 usual traceback""").tag(config=True) 

249 

250 # The class to use as the crash handler. 

251 crash_handler_class = Type(crashhandler.CrashHandler) 

252 

253 @catch_config_error 

254 def __init__(self, **kwargs): 

255 super(BaseIPythonApplication, self).__init__(**kwargs) 

256 # ensure current working directory exists 

257 try: 

258 os.getcwd() 

259 except: 

260 # exit if cwd doesn't exist 

261 self.log.error("Current working directory doesn't exist.") 

262 self.exit(1) 

263 

264 #------------------------------------------------------------------------- 

265 # Various stages of Application creation 

266 #------------------------------------------------------------------------- 

267 

268 def init_crash_handler(self): 

269 """Create a crash handler, typically setting sys.excepthook to it.""" 

270 self.crash_handler = self.crash_handler_class(self) 

271 sys.excepthook = self.excepthook 

272 def unset_crashhandler(): 

273 sys.excepthook = sys.__excepthook__ 

274 atexit.register(unset_crashhandler) 

275 

276 def excepthook(self, etype, evalue, tb): 

277 """this is sys.excepthook after init_crashhandler 

278 

279 set self.verbose_crash=True to use our full crashhandler, instead of 

280 a regular traceback with a short message (crash_handler_lite) 

281 """ 

282 

283 if self.verbose_crash: 

284 return self.crash_handler(etype, evalue, tb) 

285 else: 

286 return crashhandler.crash_handler_lite(etype, evalue, tb) 

287 

288 @observe('ipython_dir') 

289 def _ipython_dir_changed(self, change): 

290 old = change['old'] 

291 new = change['new'] 

292 if old is not Undefined: 

293 str_old = os.path.abspath(old) 

294 if str_old in sys.path: 

295 sys.path.remove(str_old) 

296 if self.add_ipython_dir_to_sys_path: 

297 str_path = os.path.abspath(new) 

298 sys.path.append(str_path) 

299 ensure_dir_exists(new) 

300 readme = os.path.join(new, "README") 

301 readme_src = os.path.join( 

302 get_ipython_package_dir(), "config", "profile", "README" 

303 ) 

304 if not os.path.exists(readme) and os.path.exists(readme_src): 

305 shutil.copy(readme_src, readme) 

306 for d in ("extensions", "nbextensions"): 

307 path = os.path.join(new, d) 

308 try: 

309 ensure_dir_exists(path) 

310 except OSError as e: 

311 # this will not be EEXIST 

312 self.log.error("couldn't create path %s: %s", path, e) 

313 self.log.debug("IPYTHONDIR set to: %s", new) 

314 

315 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): 

316 """Load the config file. 

317 

318 By default, errors in loading config are handled, and a warning 

319 printed on screen. For testing, the suppress_errors option is set 

320 to False, so errors will make tests fail. 

321 

322 `suppress_errors` default value is to be `None` in which case the 

323 behavior default to the one of `traitlets.Application`. 

324 

325 The default value can be set : 

326 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). 

327 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). 

328 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. 

329 

330 Any other value are invalid, and will make IPython exit with a non-zero return code. 

331 """ 

332 

333 

334 self.log.debug("Searching path %s for config files", self.config_file_paths) 

335 base_config = 'ipython_config.py' 

336 self.log.debug("Attempting to load config file: %s" % 

337 base_config) 

338 try: 

339 if suppress_errors is not None: 

340 old_value = Application.raise_config_file_errors 

341 Application.raise_config_file_errors = not suppress_errors; 

342 Application.load_config_file( 

343 self, 

344 base_config, 

345 path=self.config_file_paths 

346 ) 

347 except ConfigFileNotFound: 

348 # ignore errors loading parent 

349 self.log.debug("Config file %s not found", base_config) 

350 pass 

351 if suppress_errors is not None: 

352 Application.raise_config_file_errors = old_value 

353 

354 for config_file_name in self.config_files: 

355 if not config_file_name or config_file_name == base_config: 

356 continue 

357 self.log.debug("Attempting to load config file: %s" % 

358 self.config_file_name) 

359 try: 

360 Application.load_config_file( 

361 self, 

362 config_file_name, 

363 path=self.config_file_paths 

364 ) 

365 except ConfigFileNotFound: 

366 # Only warn if the default config file was NOT being used. 

367 if config_file_name in self.config_file_specified: 

368 msg = self.log.warning 

369 else: 

370 msg = self.log.debug 

371 msg("Config file not found, skipping: %s", config_file_name) 

372 except Exception: 

373 # For testing purposes. 

374 if not suppress_errors: 

375 raise 

376 self.log.warning("Error loading config file: %s" % 

377 self.config_file_name, exc_info=True) 

378 

379 def init_profile_dir(self): 

380 """initialize the profile dir""" 

381 self._in_init_profile_dir = True 

382 if self.profile_dir is not None: 

383 # already ran 

384 return 

385 if 'ProfileDir.location' not in self.config: 

386 # location not specified, find by profile name 

387 try: 

388 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) 

389 except ProfileDirError: 

390 # not found, maybe create it (always create default profile) 

391 if self.auto_create or self.profile == 'default': 

392 try: 

393 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) 

394 except ProfileDirError: 

395 self.log.fatal("Could not create profile: %r"%self.profile) 

396 self.exit(1) 

397 else: 

398 self.log.info("Created profile dir: %r"%p.location) 

399 else: 

400 self.log.fatal("Profile %r not found."%self.profile) 

401 self.exit(1) 

402 else: 

403 self.log.debug("Using existing profile dir: %r", p.location) 

404 else: 

405 location = self.config.ProfileDir.location 

406 # location is fully specified 

407 try: 

408 p = ProfileDir.find_profile_dir(location, self.config) 

409 except ProfileDirError: 

410 # not found, maybe create it 

411 if self.auto_create: 

412 try: 

413 p = ProfileDir.create_profile_dir(location, self.config) 

414 except ProfileDirError: 

415 self.log.fatal("Could not create profile directory: %r"%location) 

416 self.exit(1) 

417 else: 

418 self.log.debug("Creating new profile dir: %r"%location) 

419 else: 

420 self.log.fatal("Profile directory %r not found."%location) 

421 self.exit(1) 

422 else: 

423 self.log.debug("Using existing profile dir: %r", p.location) 

424 # if profile_dir is specified explicitly, set profile name 

425 dir_name = os.path.basename(p.location) 

426 if dir_name.startswith('profile_'): 

427 self.profile = dir_name[8:] 

428 

429 self.profile_dir = p 

430 self.config_file_paths.append(p.location) 

431 self._in_init_profile_dir = False 

432 

433 def init_config_files(self): 

434 """[optionally] copy default config files into profile dir.""" 

435 self.config_file_paths.extend(ENV_CONFIG_DIRS) 

436 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) 

437 # copy config files 

438 path = Path(self.builtin_profile_dir) 

439 if self.copy_config_files: 

440 src = self.profile 

441 

442 cfg = self.config_file_name 

443 if path and (path / cfg).exists(): 

444 self.log.warning( 

445 "Staging %r from %s into %r [overwrite=%s]" 

446 % (cfg, src, self.profile_dir.location, self.overwrite) 

447 ) 

448 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) 

449 else: 

450 self.stage_default_config_file() 

451 else: 

452 # Still stage *bundled* config files, but not generated ones 

453 # This is necessary for `ipython profile=sympy` to load the profile 

454 # on the first go 

455 files = path.glob("*.py") 

456 for fullpath in files: 

457 cfg = fullpath.name 

458 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): 

459 # file was copied 

460 self.log.warning("Staging bundled %s from %s into %r"%( 

461 cfg, self.profile, self.profile_dir.location) 

462 ) 

463 

464 

465 def stage_default_config_file(self): 

466 """auto generate default config file, and stage it into the profile.""" 

467 s = self.generate_config_file() 

468 config_file = Path(self.profile_dir.location) / self.config_file_name 

469 if self.overwrite or not config_file.exists(): 

470 self.log.warning("Generating default config file: %r", (config_file)) 

471 config_file.write_text(s, encoding="utf-8") 

472 

473 @catch_config_error 

474 def initialize(self, argv=None): 

475 # don't hook up crash handler before parsing command-line 

476 self.parse_command_line(argv) 

477 self.init_crash_handler() 

478 if self.subapp is not None: 

479 # stop here if subapp is taking over 

480 return 

481 # save a copy of CLI config to re-load after config files 

482 # so that it has highest priority 

483 cl_config = deepcopy(self.config) 

484 self.init_profile_dir() 

485 self.init_config_files() 

486 self.load_config_file() 

487 # enforce cl-opts override configfile opts: 

488 self.update_config(cl_config)