##// END OF EJS Templates
Fix case where programdata is None on windows....
Matthias Bussonnier -
Show More
@@ -1,476 +1,476 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 programdata = Path(os.environ.get("PROGRAMDATA", None))
38 if programdata:
39 SYSTEM_CONFIG_DIRS = [str(programdata / "ipython")]
37 programdata = os.environ.get("PROGRAMDATA", None)
38 if programdata is not None:
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 188 ipython_dir = Unicode(
189 189 help="""
190 190 The name of the IPython directory. This directory is used for logging
191 191 configuration (through profiles), history storage, etc. The default
192 192 is usually $HOME/.ipython. This option can also be specified through
193 193 the environment variable IPYTHONDIR.
194 194 """
195 195 ).tag(config=True)
196 196 @default('ipython_dir')
197 197 def _ipython_dir_default(self):
198 198 d = get_ipython_dir()
199 199 self._ipython_dir_changed({
200 200 'name': 'ipython_dir',
201 201 'old': d,
202 202 'new': d,
203 203 })
204 204 return d
205 205
206 206 _in_init_profile_dir = False
207 207 profile_dir = Instance(ProfileDir, allow_none=True)
208 208 @default('profile_dir')
209 209 def _profile_dir_default(self):
210 210 # avoid recursion
211 211 if self._in_init_profile_dir:
212 212 return
213 213 # profile_dir requested early, force initialization
214 214 self.init_profile_dir()
215 215 return self.profile_dir
216 216
217 217 overwrite = Bool(False,
218 218 help="""Whether to overwrite existing config files when copying"""
219 219 ).tag(config=True)
220 220 auto_create = Bool(False,
221 221 help="""Whether to create profile dir if it doesn't exist"""
222 222 ).tag(config=True)
223 223
224 224 config_files = List(Unicode())
225 225 @default('config_files')
226 226 def _config_files_default(self):
227 227 return [self.config_file_name]
228 228
229 229 copy_config_files = Bool(False,
230 230 help="""Whether to install the default config files into the profile dir.
231 231 If a new profile is being created, and IPython contains config files for that
232 232 profile, then they will be staged into the new directory. Otherwise,
233 233 default config files will be automatically generated.
234 234 """).tag(config=True)
235 235
236 236 verbose_crash = Bool(False,
237 237 help="""Create a massive crash report when IPython encounters what may be an
238 238 internal error. The default is to append a short message to the
239 239 usual traceback""").tag(config=True)
240 240
241 241 # The class to use as the crash handler.
242 242 crash_handler_class = Type(crashhandler.CrashHandler)
243 243
244 244 @catch_config_error
245 245 def __init__(self, **kwargs):
246 246 super(BaseIPythonApplication, self).__init__(**kwargs)
247 247 # ensure current working directory exists
248 248 try:
249 249 os.getcwd()
250 250 except:
251 251 # exit if cwd doesn't exist
252 252 self.log.error("Current working directory doesn't exist.")
253 253 self.exit(1)
254 254
255 255 #-------------------------------------------------------------------------
256 256 # Various stages of Application creation
257 257 #-------------------------------------------------------------------------
258 258
259 259 def init_crash_handler(self):
260 260 """Create a crash handler, typically setting sys.excepthook to it."""
261 261 self.crash_handler = self.crash_handler_class(self)
262 262 sys.excepthook = self.excepthook
263 263 def unset_crashhandler():
264 264 sys.excepthook = sys.__excepthook__
265 265 atexit.register(unset_crashhandler)
266 266
267 267 def excepthook(self, etype, evalue, tb):
268 268 """this is sys.excepthook after init_crashhandler
269 269
270 270 set self.verbose_crash=True to use our full crashhandler, instead of
271 271 a regular traceback with a short message (crash_handler_lite)
272 272 """
273 273
274 274 if self.verbose_crash:
275 275 return self.crash_handler(etype, evalue, tb)
276 276 else:
277 277 return crashhandler.crash_handler_lite(etype, evalue, tb)
278 278
279 279 @observe('ipython_dir')
280 280 def _ipython_dir_changed(self, change):
281 281 old = change['old']
282 282 new = change['new']
283 283 if old is not Undefined:
284 284 str_old = os.path.abspath(old)
285 285 if str_old in sys.path:
286 286 sys.path.remove(str_old)
287 287 str_path = os.path.abspath(new)
288 288 sys.path.append(str_path)
289 289 ensure_dir_exists(new)
290 290 readme = os.path.join(new, 'README')
291 291 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
292 292 if not os.path.exists(readme) and os.path.exists(readme_src):
293 293 shutil.copy(readme_src, readme)
294 294 for d in ('extensions', 'nbextensions'):
295 295 path = os.path.join(new, d)
296 296 try:
297 297 ensure_dir_exists(path)
298 298 except OSError as e:
299 299 # this will not be EEXIST
300 300 self.log.error("couldn't create path %s: %s", path, e)
301 301 self.log.debug("IPYTHONDIR set to: %s" % new)
302 302
303 303 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
304 304 """Load the config file.
305 305
306 306 By default, errors in loading config are handled, and a warning
307 307 printed on screen. For testing, the suppress_errors option is set
308 308 to False, so errors will make tests fail.
309 309
310 310 `suppress_errors` default value is to be `None` in which case the
311 311 behavior default to the one of `traitlets.Application`.
312 312
313 313 The default value can be set :
314 314 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
315 315 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
316 316 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
317 317
318 318 Any other value are invalid, and will make IPython exit with a non-zero return code.
319 319 """
320 320
321 321
322 322 self.log.debug("Searching path %s for config files", self.config_file_paths)
323 323 base_config = 'ipython_config.py'
324 324 self.log.debug("Attempting to load config file: %s" %
325 325 base_config)
326 326 try:
327 327 if suppress_errors is not None:
328 328 old_value = Application.raise_config_file_errors
329 329 Application.raise_config_file_errors = not suppress_errors;
330 330 Application.load_config_file(
331 331 self,
332 332 base_config,
333 333 path=self.config_file_paths
334 334 )
335 335 except ConfigFileNotFound:
336 336 # ignore errors loading parent
337 337 self.log.debug("Config file %s not found", base_config)
338 338 pass
339 339 if suppress_errors is not None:
340 340 Application.raise_config_file_errors = old_value
341 341
342 342 for config_file_name in self.config_files:
343 343 if not config_file_name or config_file_name == base_config:
344 344 continue
345 345 self.log.debug("Attempting to load config file: %s" %
346 346 self.config_file_name)
347 347 try:
348 348 Application.load_config_file(
349 349 self,
350 350 config_file_name,
351 351 path=self.config_file_paths
352 352 )
353 353 except ConfigFileNotFound:
354 354 # Only warn if the default config file was NOT being used.
355 355 if config_file_name in self.config_file_specified:
356 356 msg = self.log.warning
357 357 else:
358 358 msg = self.log.debug
359 359 msg("Config file not found, skipping: %s", config_file_name)
360 360 except Exception:
361 361 # For testing purposes.
362 362 if not suppress_errors:
363 363 raise
364 364 self.log.warning("Error loading config file: %s" %
365 365 self.config_file_name, exc_info=True)
366 366
367 367 def init_profile_dir(self):
368 368 """initialize the profile dir"""
369 369 self._in_init_profile_dir = True
370 370 if self.profile_dir is not None:
371 371 # already ran
372 372 return
373 373 if 'ProfileDir.location' not in self.config:
374 374 # location not specified, find by profile name
375 375 try:
376 376 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
377 377 except ProfileDirError:
378 378 # not found, maybe create it (always create default profile)
379 379 if self.auto_create or self.profile == 'default':
380 380 try:
381 381 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
382 382 except ProfileDirError:
383 383 self.log.fatal("Could not create profile: %r"%self.profile)
384 384 self.exit(1)
385 385 else:
386 386 self.log.info("Created profile dir: %r"%p.location)
387 387 else:
388 388 self.log.fatal("Profile %r not found."%self.profile)
389 389 self.exit(1)
390 390 else:
391 391 self.log.debug(f"Using existing profile dir: {p.location!r}")
392 392 else:
393 393 location = self.config.ProfileDir.location
394 394 # location is fully specified
395 395 try:
396 396 p = ProfileDir.find_profile_dir(location, self.config)
397 397 except ProfileDirError:
398 398 # not found, maybe create it
399 399 if self.auto_create:
400 400 try:
401 401 p = ProfileDir.create_profile_dir(location, self.config)
402 402 except ProfileDirError:
403 403 self.log.fatal("Could not create profile directory: %r"%location)
404 404 self.exit(1)
405 405 else:
406 406 self.log.debug("Creating new profile dir: %r"%location)
407 407 else:
408 408 self.log.fatal("Profile directory %r not found."%location)
409 409 self.exit(1)
410 410 else:
411 411 self.log.debug(f"Using existing profile dir: {p.location!r}")
412 412 # if profile_dir is specified explicitly, set profile name
413 413 dir_name = os.path.basename(p.location)
414 414 if dir_name.startswith('profile_'):
415 415 self.profile = dir_name[8:]
416 416
417 417 self.profile_dir = p
418 418 self.config_file_paths.append(p.location)
419 419 self._in_init_profile_dir = False
420 420
421 421 def init_config_files(self):
422 422 """[optionally] copy default config files into profile dir."""
423 423 self.config_file_paths.extend(ENV_CONFIG_DIRS)
424 424 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
425 425 # copy config files
426 426 path = Path(self.builtin_profile_dir)
427 427 if self.copy_config_files:
428 428 src = self.profile
429 429
430 430 cfg = self.config_file_name
431 431 if path and (path / cfg).exists():
432 432 self.log.warning(
433 433 "Staging %r from %s into %r [overwrite=%s]"
434 434 % (cfg, src, self.profile_dir.location, self.overwrite)
435 435 )
436 436 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
437 437 else:
438 438 self.stage_default_config_file()
439 439 else:
440 440 # Still stage *bundled* config files, but not generated ones
441 441 # This is necessary for `ipython profile=sympy` to load the profile
442 442 # on the first go
443 443 files = path.glob("*.py")
444 444 for fullpath in files:
445 445 cfg = fullpath.name
446 446 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
447 447 # file was copied
448 448 self.log.warning("Staging bundled %s from %s into %r"%(
449 449 cfg, self.profile, self.profile_dir.location)
450 450 )
451 451
452 452
453 453 def stage_default_config_file(self):
454 454 """auto generate default config file, and stage it into the profile."""
455 455 s = self.generate_config_file()
456 456 config_file = Path(self.profile_dir.location) / self.config_file_name
457 457 if self.overwrite or not config_file.exists():
458 458 self.log.warning("Generating default config file: %r" % (config_file))
459 459 config_file.write_text(s)
460 460
461 461 @catch_config_error
462 462 def initialize(self, argv=None):
463 463 # don't hook up crash handler before parsing command-line
464 464 self.parse_command_line(argv)
465 465 self.init_crash_handler()
466 466 if self.subapp is not None:
467 467 # stop here if subapp is taking over
468 468 return
469 469 # save a copy of CLI config to re-load after config files
470 470 # so that it has highest priority
471 471 cl_config = deepcopy(self.config)
472 472 self.init_profile_dir()
473 473 self.init_config_files()
474 474 self.load_config_file()
475 475 # enforce cl-opts override configfile opts:
476 476 self.update_config(cl_config)
General Comments 0
You need to be logged in to leave comments. Login now