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