##// END OF EJS Templates
match log message for profile by name and path...
Min RK -
Show More
@@ -1,486 +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 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 deprecated_subcommands = {}
260 260
261 261 def initialize_subcommand(self, subc, argv=None):
262 262 if subc in self.deprecated_subcommands:
263 263 self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed "
264 264 "in future versions.".format(sub=subc))
265 265 self.log.warning("You likely want to use `jupyter {sub}` in the "
266 266 "future".format(sub=subc))
267 267 return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv)
268 268
269 269 def init_crash_handler(self):
270 270 """Create a crash handler, typically setting sys.excepthook to it."""
271 271 self.crash_handler = self.crash_handler_class(self)
272 272 sys.excepthook = self.excepthook
273 273 def unset_crashhandler():
274 274 sys.excepthook = sys.__excepthook__
275 275 atexit.register(unset_crashhandler)
276 276
277 277 def excepthook(self, etype, evalue, tb):
278 278 """this is sys.excepthook after init_crashhandler
279 279
280 280 set self.verbose_crash=True to use our full crashhandler, instead of
281 281 a regular traceback with a short message (crash_handler_lite)
282 282 """
283 283
284 284 if self.verbose_crash:
285 285 return self.crash_handler(etype, evalue, tb)
286 286 else:
287 287 return crashhandler.crash_handler_lite(etype, evalue, tb)
288 288
289 289 @observe('ipython_dir')
290 290 def _ipython_dir_changed(self, change):
291 291 old = change['old']
292 292 new = change['new']
293 293 if old is not Undefined:
294 294 str_old = os.path.abspath(old)
295 295 if str_old in sys.path:
296 296 sys.path.remove(str_old)
297 297 str_path = os.path.abspath(new)
298 298 sys.path.append(str_path)
299 299 ensure_dir_exists(new)
300 300 readme = os.path.join(new, 'README')
301 301 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
302 302 if not os.path.exists(readme) and os.path.exists(readme_src):
303 303 shutil.copy(readme_src, readme)
304 304 for d in ('extensions', 'nbextensions'):
305 305 path = os.path.join(new, d)
306 306 try:
307 307 ensure_dir_exists(path)
308 308 except OSError as e:
309 309 # this will not be EEXIST
310 310 self.log.error("couldn't create path %s: %s", path, e)
311 311 self.log.debug("IPYTHONDIR set to: %s" % new)
312 312
313 313 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
314 314 """Load the config file.
315 315
316 316 By default, errors in loading config are handled, and a warning
317 317 printed on screen. For testing, the suppress_errors option is set
318 318 to False, so errors will make tests fail.
319 319
320 320 `suppress_errors` default value is to be `None` in which case the
321 321 behavior default to the one of `traitlets.Application`.
322 322
323 323 The default value can be set :
324 324 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
325 325 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
326 326 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
327 327
328 328 Any other value are invalid, and will make IPython exit with a non-zero return code.
329 329 """
330 330
331 331
332 332 self.log.debug("Searching path %s for config files", self.config_file_paths)
333 333 base_config = 'ipython_config.py'
334 334 self.log.debug("Attempting to load config file: %s" %
335 335 base_config)
336 336 try:
337 337 if suppress_errors is not None:
338 338 old_value = Application.raise_config_file_errors
339 339 Application.raise_config_file_errors = not suppress_errors;
340 340 Application.load_config_file(
341 341 self,
342 342 base_config,
343 343 path=self.config_file_paths
344 344 )
345 345 except ConfigFileNotFound:
346 346 # ignore errors loading parent
347 347 self.log.debug("Config file %s not found", base_config)
348 348 pass
349 349 if suppress_errors is not None:
350 350 Application.raise_config_file_errors = old_value
351 351
352 352 for config_file_name in self.config_files:
353 353 if not config_file_name or config_file_name == base_config:
354 354 continue
355 355 self.log.debug("Attempting to load config file: %s" %
356 356 self.config_file_name)
357 357 try:
358 358 Application.load_config_file(
359 359 self,
360 360 config_file_name,
361 361 path=self.config_file_paths
362 362 )
363 363 except ConfigFileNotFound:
364 364 # Only warn if the default config file was NOT being used.
365 365 if config_file_name in self.config_file_specified:
366 366 msg = self.log.warning
367 367 else:
368 368 msg = self.log.debug
369 369 msg("Config file not found, skipping: %s", config_file_name)
370 370 except Exception:
371 371 # For testing purposes.
372 372 if not suppress_errors:
373 373 raise
374 374 self.log.warning("Error loading config file: %s" %
375 375 self.config_file_name, exc_info=True)
376 376
377 377 def init_profile_dir(self):
378 378 """initialize the profile dir"""
379 379 self._in_init_profile_dir = True
380 380 if self.profile_dir is not None:
381 381 # already ran
382 382 return
383 383 if 'ProfileDir.location' not in self.config:
384 384 # location not specified, find by profile name
385 385 try:
386 386 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
387 387 except ProfileDirError:
388 388 # not found, maybe create it (always create default profile)
389 389 if self.auto_create or self.profile == 'default':
390 390 try:
391 391 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
392 392 except ProfileDirError:
393 393 self.log.fatal("Could not create profile: %r"%self.profile)
394 394 self.exit(1)
395 395 else:
396 396 self.log.info("Created profile dir: %r"%p.location)
397 397 else:
398 398 self.log.fatal("Profile %r not found."%self.profile)
399 399 self.exit(1)
400 400 else:
401 self.log.debug("Using existing profile dir: %r"%p.location)
401 self.log.debug(f"Using existing profile dir: {p.location!r}")
402 402 else:
403 403 location = self.config.ProfileDir.location
404 404 # location is fully specified
405 405 try:
406 406 p = ProfileDir.find_profile_dir(location, self.config)
407 407 except ProfileDirError:
408 408 # not found, maybe create it
409 409 if self.auto_create:
410 410 try:
411 411 p = ProfileDir.create_profile_dir(location, self.config)
412 412 except ProfileDirError:
413 413 self.log.fatal("Could not create profile directory: %r"%location)
414 414 self.exit(1)
415 415 else:
416 416 self.log.debug("Creating new profile dir: %r"%location)
417 417 else:
418 418 self.log.fatal("Profile directory %r not found."%location)
419 419 self.exit(1)
420 420 else:
421 self.log.info("Using existing profile dir: %r"%location)
421 self.log.debug(f"Using existing profile dir: {p.location!r}")
422 422 # if profile_dir is specified explicitly, set profile name
423 423 dir_name = os.path.basename(p.location)
424 424 if dir_name.startswith('profile_'):
425 425 self.profile = dir_name[8:]
426 426
427 427 self.profile_dir = p
428 428 self.config_file_paths.append(p.location)
429 429 self._in_init_profile_dir = False
430 430
431 431 def init_config_files(self):
432 432 """[optionally] copy default config files into profile dir."""
433 433 self.config_file_paths.extend(ENV_CONFIG_DIRS)
434 434 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
435 435 # copy config files
436 436 path = Path(self.builtin_profile_dir)
437 437 if self.copy_config_files:
438 438 src = self.profile
439 439
440 440 cfg = self.config_file_name
441 441 if path and (path / cfg).exists():
442 442 self.log.warning(
443 443 "Staging %r from %s into %r [overwrite=%s]"
444 444 % (cfg, src, self.profile_dir.location, self.overwrite)
445 445 )
446 446 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
447 447 else:
448 448 self.stage_default_config_file()
449 449 else:
450 450 # Still stage *bundled* config files, but not generated ones
451 451 # This is necessary for `ipython profile=sympy` to load the profile
452 452 # on the first go
453 453 files = path.glob("*.py")
454 454 for fullpath in files:
455 455 cfg = fullpath.name
456 456 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
457 457 # file was copied
458 458 self.log.warning("Staging bundled %s from %s into %r"%(
459 459 cfg, self.profile, self.profile_dir.location)
460 460 )
461 461
462 462
463 463 def stage_default_config_file(self):
464 464 """auto generate default config file, and stage it into the profile."""
465 465 s = self.generate_config_file()
466 466 config_file = Path(self.profile_dir.location) / self.config_file_name
467 467 if self.overwrite or not config_file.exists():
468 468 self.log.warning("Generating default config file: %r" % (config_file))
469 469 config_file.write_text(s)
470 470
471 471 @catch_config_error
472 472 def initialize(self, argv=None):
473 473 # don't hook up crash handler before parsing command-line
474 474 self.parse_command_line(argv)
475 475 self.init_crash_handler()
476 476 if self.subapp is not None:
477 477 # stop here if subapp is taking over
478 478 return
479 479 # save a copy of CLI config to re-load after config files
480 480 # so that it has highest priority
481 481 cl_config = deepcopy(self.config)
482 482 self.init_profile_dir()
483 483 self.init_config_files()
484 484 self.load_config_file()
485 485 # enforce cl-opts override configfile opts:
486 486 self.update_config(cl_config)
General Comments 0
You need to be logged in to leave comments. Login now