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