##// END OF EJS Templates
Fix #14282, and please linter.
Matthias Bussonnier -
Show More
@@ -1,488 +1,492 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 logging
18 18 import os
19 19 import shutil
20 20 import sys
21 21
22 22 from pathlib import Path
23 23
24 24 from traitlets.config.application import Application, catch_config_error
25 25 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
26 26 from IPython.core import release, crashhandler
27 27 from IPython.core.profiledir import ProfileDir, ProfileDirError
28 28 from IPython.paths import get_ipython_dir, get_ipython_package_dir
29 29 from IPython.utils.path import ensure_dir_exists
30 30 from traitlets import (
31 31 List, Unicode, Type, Bool, Set, Instance, Undefined,
32 32 default, observe,
33 33 )
34 34
35 35 if os.name == "nt":
36 36 programdata = os.environ.get("PROGRAMDATA", None)
37 37 if programdata is not None:
38 38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
39 39 else: # PROGRAMDATA is not defined by default on XP.
40 40 SYSTEM_CONFIG_DIRS = []
41 41 else:
42 42 SYSTEM_CONFIG_DIRS = [
43 43 "/usr/local/etc/ipython",
44 44 "/etc/ipython",
45 45 ]
46 46
47 47
48 48 ENV_CONFIG_DIRS = []
49 49 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
50 50 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
51 51 # only add ENV_CONFIG if sys.prefix is not already included
52 52 ENV_CONFIG_DIRS.append(_env_config_dir)
53 53
54 54
55 55 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
56 56 if _envvar in {None, ''}:
57 57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
58 58 else:
59 59 if _envvar.lower() in {'1','true'}:
60 60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
61 61 elif _envvar.lower() in {'0','false'} :
62 62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
63 63 else:
64 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 65
66 66 # aliases and flags
67 67
68 68 base_aliases = {}
69 69 if isinstance(Application.aliases, dict):
70 70 # traitlets 5
71 71 base_aliases.update(Application.aliases)
72 72 base_aliases.update(
73 73 {
74 74 "profile-dir": "ProfileDir.location",
75 75 "profile": "BaseIPythonApplication.profile",
76 76 "ipython-dir": "BaseIPythonApplication.ipython_dir",
77 77 "log-level": "Application.log_level",
78 78 "config": "BaseIPythonApplication.extra_config_file",
79 79 }
80 80 )
81 81
82 82 base_flags = dict()
83 83 if isinstance(Application.flags, dict):
84 84 # traitlets 5
85 85 base_flags.update(Application.flags)
86 86 base_flags.update(
87 87 dict(
88 88 debug=(
89 89 {"Application": {"log_level": logging.DEBUG}},
90 90 "set log level to logging.DEBUG (maximize logging output)",
91 91 ),
92 92 quiet=(
93 93 {"Application": {"log_level": logging.CRITICAL}},
94 94 "set log level to logging.CRITICAL (minimize logging output)",
95 95 ),
96 96 init=(
97 97 {
98 98 "BaseIPythonApplication": {
99 99 "copy_config_files": True,
100 100 "auto_create": True,
101 101 }
102 102 },
103 103 """Initialize profile with default config files. This is equivalent
104 104 to running `ipython profile create <profile>` prior to startup.
105 105 """,
106 106 ),
107 107 )
108 108 )
109 109
110 110
111 111 class ProfileAwareConfigLoader(PyFileConfigLoader):
112 112 """A Python file config loader that is aware of IPython profiles."""
113 113 def load_subconfig(self, fname, path=None, profile=None):
114 114 if profile is not None:
115 115 try:
116 116 profile_dir = ProfileDir.find_profile_dir_by_name(
117 117 get_ipython_dir(),
118 118 profile,
119 119 )
120 120 except ProfileDirError:
121 121 return
122 122 path = profile_dir.location
123 123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
124 124
125 125 class BaseIPythonApplication(Application):
126 126 name = "ipython"
127 127 description = "IPython: an enhanced interactive Python shell."
128 128 version = Unicode(release.version)
129 129
130 130 aliases = base_aliases
131 131 flags = base_flags
132 132 classes = List([ProfileDir])
133 133
134 134 # enable `load_subconfig('cfg.py', profile='name')`
135 135 python_config_loader_class = ProfileAwareConfigLoader
136 136
137 137 # Track whether the config_file has changed,
138 138 # because some logic happens only if we aren't using the default.
139 139 config_file_specified = Set()
140 140
141 141 config_file_name = Unicode()
142 142 @default('config_file_name')
143 143 def _config_file_name_default(self):
144 144 return self.name.replace('-','_') + u'_config.py'
145 145 @observe('config_file_name')
146 146 def _config_file_name_changed(self, change):
147 147 if change['new'] != change['old']:
148 148 self.config_file_specified.add(change['new'])
149 149
150 150 # The directory that contains IPython's builtin profiles.
151 151 builtin_profile_dir = Unicode(
152 152 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
153 153 )
154 154
155 155 config_file_paths = List(Unicode())
156 156 @default('config_file_paths')
157 157 def _config_file_paths_default(self):
158 158 return []
159 159
160 160 extra_config_file = Unicode(
161 161 help="""Path to an extra config file to load.
162 162
163 163 If specified, load this config file in addition to any other IPython config.
164 164 """).tag(config=True)
165 165 @observe('extra_config_file')
166 166 def _extra_config_file_changed(self, change):
167 167 old = change['old']
168 168 new = change['new']
169 169 try:
170 170 self.config_files.remove(old)
171 171 except ValueError:
172 172 pass
173 173 self.config_file_specified.add(new)
174 174 self.config_files.append(new)
175 175
176 176 profile = Unicode(u'default',
177 177 help="""The IPython profile to use."""
178 178 ).tag(config=True)
179 179
180 180 @observe('profile')
181 181 def _profile_changed(self, change):
182 182 self.builtin_profile_dir = os.path.join(
183 183 get_ipython_package_dir(), u'config', u'profile', change['new']
184 184 )
185 185
186 186 add_ipython_dir_to_sys_path = Bool(
187 187 False,
188 188 """Should the IPython profile directory be added to sys path ?
189 189
190 190 This option was non-existing before IPython 8.0, and ipython_dir was added to
191 191 sys path to allow import of extensions present there. This was historical
192 192 baggage from when pip did not exist. This now default to false,
193 193 but can be set to true for legacy reasons.
194 194 """,
195 195 ).tag(config=True)
196 196
197 197 ipython_dir = Unicode(
198 198 help="""
199 199 The name of the IPython directory. This directory is used for logging
200 200 configuration (through profiles), history storage, etc. The default
201 201 is usually $HOME/.ipython. This option can also be specified through
202 202 the environment variable IPYTHONDIR.
203 203 """
204 204 ).tag(config=True)
205 205 @default('ipython_dir')
206 206 def _ipython_dir_default(self):
207 207 d = get_ipython_dir()
208 208 self._ipython_dir_changed({
209 209 'name': 'ipython_dir',
210 210 'old': d,
211 211 'new': d,
212 212 })
213 213 return d
214 214
215 215 _in_init_profile_dir = False
216
216 217 profile_dir = Instance(ProfileDir, allow_none=True)
218
217 219 @default('profile_dir')
218 220 def _profile_dir_default(self):
219 221 # avoid recursion
220 222 if self._in_init_profile_dir:
221 223 return
222 224 # profile_dir requested early, force initialization
223 225 self.init_profile_dir()
224 226 return self.profile_dir
225 227
226 228 overwrite = Bool(False,
227 229 help="""Whether to overwrite existing config files when copying"""
228 230 ).tag(config=True)
231
229 232 auto_create = Bool(False,
230 233 help="""Whether to create profile dir if it doesn't exist"""
231 234 ).tag(config=True)
232 235
233 236 config_files = List(Unicode())
237
234 238 @default('config_files')
235 239 def _config_files_default(self):
236 240 return [self.config_file_name]
237 241
238 242 copy_config_files = Bool(False,
239 243 help="""Whether to install the default config files into the profile dir.
240 244 If a new profile is being created, and IPython contains config files for that
241 245 profile, then they will be staged into the new directory. Otherwise,
242 246 default config files will be automatically generated.
243 247 """).tag(config=True)
244 248
245 249 verbose_crash = Bool(False,
246 250 help="""Create a massive crash report when IPython encounters what may be an
247 251 internal error. The default is to append a short message to the
248 252 usual traceback""").tag(config=True)
249 253
250 254 # The class to use as the crash handler.
251 255 crash_handler_class = Type(crashhandler.CrashHandler)
252 256
253 257 @catch_config_error
254 258 def __init__(self, **kwargs):
255 259 super(BaseIPythonApplication, self).__init__(**kwargs)
256 260 # ensure current working directory exists
257 261 try:
258 262 os.getcwd()
259 263 except:
260 264 # exit if cwd doesn't exist
261 265 self.log.error("Current working directory doesn't exist.")
262 266 self.exit(1)
263 267
264 268 #-------------------------------------------------------------------------
265 269 # Various stages of Application creation
266 270 #-------------------------------------------------------------------------
267 271
268 272 def init_crash_handler(self):
269 273 """Create a crash handler, typically setting sys.excepthook to it."""
270 274 self.crash_handler = self.crash_handler_class(self)
271 275 sys.excepthook = self.excepthook
272 276 def unset_crashhandler():
273 277 sys.excepthook = sys.__excepthook__
274 278 atexit.register(unset_crashhandler)
275 279
276 280 def excepthook(self, etype, evalue, tb):
277 281 """this is sys.excepthook after init_crashhandler
278 282
279 283 set self.verbose_crash=True to use our full crashhandler, instead of
280 284 a regular traceback with a short message (crash_handler_lite)
281 285 """
282 286
283 287 if self.verbose_crash:
284 288 return self.crash_handler(etype, evalue, tb)
285 289 else:
286 290 return crashhandler.crash_handler_lite(etype, evalue, tb)
287 291
288 292 @observe('ipython_dir')
289 293 def _ipython_dir_changed(self, change):
290 294 old = change['old']
291 295 new = change['new']
292 296 if old is not Undefined:
293 297 str_old = os.path.abspath(old)
294 298 if str_old in sys.path:
295 299 sys.path.remove(str_old)
296 300 if self.add_ipython_dir_to_sys_path:
297 301 str_path = os.path.abspath(new)
298 302 sys.path.append(str_path)
299 303 ensure_dir_exists(new)
300 304 readme = os.path.join(new, "README")
301 305 readme_src = os.path.join(
302 306 get_ipython_package_dir(), "config", "profile", "README"
303 307 )
304 308 if not os.path.exists(readme) and os.path.exists(readme_src):
305 309 shutil.copy(readme_src, readme)
306 310 for d in ("extensions", "nbextensions"):
307 311 path = os.path.join(new, d)
308 312 try:
309 313 ensure_dir_exists(path)
310 314 except OSError as e:
311 315 # this will not be EEXIST
312 316 self.log.error("couldn't create path %s: %s", path, e)
313 317 self.log.debug("IPYTHONDIR set to: %s", new)
314 318
315 319 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
316 320 """Load the config file.
317 321
318 322 By default, errors in loading config are handled, and a warning
319 323 printed on screen. For testing, the suppress_errors option is set
320 324 to False, so errors will make tests fail.
321 325
322 326 `suppress_errors` default value is to be `None` in which case the
323 327 behavior default to the one of `traitlets.Application`.
324 328
325 329 The default value can be set :
326 330 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
327 331 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
328 332 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
329 333
330 334 Any other value are invalid, and will make IPython exit with a non-zero return code.
331 335 """
332 336
333 337
334 338 self.log.debug("Searching path %s for config files", self.config_file_paths)
335 339 base_config = 'ipython_config.py'
336 340 self.log.debug("Attempting to load config file: %s" %
337 341 base_config)
338 342 try:
339 343 if suppress_errors is not None:
340 344 old_value = Application.raise_config_file_errors
341 345 Application.raise_config_file_errors = not suppress_errors;
342 346 Application.load_config_file(
343 347 self,
344 348 base_config,
345 349 path=self.config_file_paths
346 350 )
347 351 except ConfigFileNotFound:
348 352 # ignore errors loading parent
349 353 self.log.debug("Config file %s not found", base_config)
350 354 pass
351 355 if suppress_errors is not None:
352 356 Application.raise_config_file_errors = old_value
353 357
354 358 for config_file_name in self.config_files:
355 359 if not config_file_name or config_file_name == base_config:
356 360 continue
357 361 self.log.debug("Attempting to load config file: %s" %
358 362 self.config_file_name)
359 363 try:
360 364 Application.load_config_file(
361 365 self,
362 366 config_file_name,
363 367 path=self.config_file_paths
364 368 )
365 369 except ConfigFileNotFound:
366 370 # Only warn if the default config file was NOT being used.
367 371 if config_file_name in self.config_file_specified:
368 372 msg = self.log.warning
369 373 else:
370 374 msg = self.log.debug
371 375 msg("Config file not found, skipping: %s", config_file_name)
372 376 except Exception:
373 377 # For testing purposes.
374 378 if not suppress_errors:
375 379 raise
376 380 self.log.warning("Error loading config file: %s" %
377 381 self.config_file_name, exc_info=True)
378 382
379 383 def init_profile_dir(self):
380 384 """initialize the profile dir"""
381 385 self._in_init_profile_dir = True
382 386 if self.profile_dir is not None:
383 387 # already ran
384 388 return
385 389 if 'ProfileDir.location' not in self.config:
386 390 # location not specified, find by profile name
387 391 try:
388 392 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
389 393 except ProfileDirError:
390 394 # not found, maybe create it (always create default profile)
391 395 if self.auto_create or self.profile == 'default':
392 396 try:
393 397 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
394 398 except ProfileDirError:
395 399 self.log.fatal("Could not create profile: %r"%self.profile)
396 400 self.exit(1)
397 401 else:
398 402 self.log.info("Created profile dir: %r"%p.location)
399 403 else:
400 404 self.log.fatal("Profile %r not found."%self.profile)
401 405 self.exit(1)
402 406 else:
403 407 self.log.debug("Using existing profile dir: %r", p.location)
404 408 else:
405 409 location = self.config.ProfileDir.location
406 410 # location is fully specified
407 411 try:
408 412 p = ProfileDir.find_profile_dir(location, self.config)
409 413 except ProfileDirError:
410 414 # not found, maybe create it
411 415 if self.auto_create:
412 416 try:
413 417 p = ProfileDir.create_profile_dir(location, self.config)
414 418 except ProfileDirError:
415 419 self.log.fatal("Could not create profile directory: %r"%location)
416 420 self.exit(1)
417 421 else:
418 422 self.log.debug("Creating new profile dir: %r"%location)
419 423 else:
420 424 self.log.fatal("Profile directory %r not found."%location)
421 425 self.exit(1)
422 426 else:
423 427 self.log.debug("Using existing profile dir: %r", p.location)
424 428 # if profile_dir is specified explicitly, set profile name
425 429 dir_name = os.path.basename(p.location)
426 430 if dir_name.startswith('profile_'):
427 431 self.profile = dir_name[8:]
428 432
429 433 self.profile_dir = p
430 434 self.config_file_paths.append(p.location)
431 435 self._in_init_profile_dir = False
432 436
433 437 def init_config_files(self):
434 438 """[optionally] copy default config files into profile dir."""
435 439 self.config_file_paths.extend(ENV_CONFIG_DIRS)
436 440 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
437 441 # copy config files
438 442 path = Path(self.builtin_profile_dir)
439 443 if self.copy_config_files:
440 444 src = self.profile
441 445
442 446 cfg = self.config_file_name
443 447 if path and (path / cfg).exists():
444 448 self.log.warning(
445 449 "Staging %r from %s into %r [overwrite=%s]"
446 450 % (cfg, src, self.profile_dir.location, self.overwrite)
447 451 )
448 452 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
449 453 else:
450 454 self.stage_default_config_file()
451 455 else:
452 456 # Still stage *bundled* config files, but not generated ones
453 457 # This is necessary for `ipython profile=sympy` to load the profile
454 458 # on the first go
455 459 files = path.glob("*.py")
456 460 for fullpath in files:
457 461 cfg = fullpath.name
458 462 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
459 463 # file was copied
460 464 self.log.warning("Staging bundled %s from %s into %r"%(
461 465 cfg, self.profile, self.profile_dir.location)
462 466 )
463 467
464 468
465 469 def stage_default_config_file(self):
466 470 """auto generate default config file, and stage it into the profile."""
467 471 s = self.generate_config_file()
468 472 config_file = Path(self.profile_dir.location) / self.config_file_name
469 473 if self.overwrite or not config_file.exists():
470 474 self.log.warning("Generating default config file: %r", (config_file))
471 475 config_file.write_text(s, encoding="utf-8")
472 476
473 477 @catch_config_error
474 478 def initialize(self, argv=None):
475 479 # don't hook up crash handler before parsing command-line
476 480 self.parse_command_line(argv)
477 481 self.init_crash_handler()
478 482 if self.subapp is not None:
479 483 # stop here if subapp is taking over
480 484 return
481 485 # save a copy of CLI config to re-load after config files
482 486 # so that it has highest priority
483 487 cl_config = deepcopy(self.config)
484 488 self.init_profile_dir()
485 489 self.init_config_files()
486 490 self.load_config_file()
487 491 # enforce cl-opts override configfile opts:
488 492 self.update_config(cl_config)
@@ -1,312 +1,312 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25
26 26 from traitlets.config.application import Application
27 27 from IPython.core.application import (
28 28 BaseIPythonApplication, base_flags
29 29 )
30 30 from IPython.core.profiledir import ProfileDir
31 31 from IPython.utils.importstring import import_item
32 32 from IPython.paths import get_ipython_dir, get_ipython_package_dir
33 33 from traitlets import Unicode, Bool, Dict, observe
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Constants
37 37 #-----------------------------------------------------------------------------
38 38
39 39 create_help = """Create an IPython profile by name
40 40
41 41 Create an ipython profile directory by its name or
42 42 profile directory path. Profile directories contain
43 43 configuration, log and security related files and are named
44 44 using the convention 'profile_<name>'. By default they are
45 45 located in your ipython directory. Once created, you will
46 46 can edit the configuration files in the profile
47 47 directory to configure IPython. Most users will create a
48 48 profile directory by name,
49 49 `ipython profile create myprofile`, which will put the directory
50 50 in `<ipython_dir>/profile_myprofile`.
51 51 """
52 52 list_help = """List available IPython profiles
53 53
54 54 List all available profiles, by profile location, that can
55 55 be found in the current working directly or in the ipython
56 56 directory. Profile directories are named using the convention
57 57 'profile_<profile>'.
58 58 """
59 59 profile_help = """Manage IPython profiles
60 60
61 61 Profile directories contain
62 62 configuration, log and security related files and are named
63 63 using the convention 'profile_<name>'. By default they are
64 64 located in your ipython directory. You can create profiles
65 65 with `ipython profile create <name>`, or see the profiles you
66 66 already have with `ipython profile list`
67 67
68 68 To get started configuring IPython, simply do:
69 69
70 70 $> ipython profile create
71 71
72 72 and IPython will create the default profile in <ipython_dir>/profile_default,
73 73 where you can edit ipython_config.py to start configuring IPython.
74 74
75 75 """
76 76
77 77 _list_examples = "ipython profile list # list all profiles"
78 78
79 79 _create_examples = """
80 80 ipython profile create foo # create profile foo w/ default config files
81 81 ipython profile create foo --reset # restage default config files over current
82 82 ipython profile create foo --parallel # also stage parallel config files
83 83 """
84 84
85 85 _main_examples = """
86 86 ipython profile create -h # show the help string for the create subcommand
87 87 ipython profile list -h # show the help string for the list subcommand
88 88
89 89 ipython locate profile foo # print the path to the directory for profile 'foo'
90 90 """
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Profile Application Class (for `ipython profile` subcommand)
94 94 #-----------------------------------------------------------------------------
95 95
96 96
97 97 def list_profiles_in(path):
98 98 """list profiles in a given root directory"""
99 99 profiles = []
100 100
101 101 # for python 3.6+ rewrite to: with os.scandir(path) as dirlist:
102 102 files = os.scandir(path)
103 103 for f in files:
104 104 if f.is_dir() and f.name.startswith('profile_'):
105 105 profiles.append(f.name.split('_', 1)[-1])
106 106 return profiles
107 107
108 108
109 109 def list_bundled_profiles():
110 110 """list profiles that are bundled with IPython."""
111 111 path = os.path.join(get_ipython_package_dir(), u'core', u'profile')
112 112 profiles = []
113 113
114 114 # for python 3.6+ rewrite to: with os.scandir(path) as dirlist:
115 115 files = os.scandir(path)
116 116 for profile in files:
117 117 if profile.is_dir() and profile.name != "__pycache__":
118 118 profiles.append(profile.name)
119 119 return profiles
120 120
121 121
122 122 class ProfileLocate(BaseIPythonApplication):
123 123 description = """print the path to an IPython profile dir"""
124 124
125 125 def parse_command_line(self, argv=None):
126 126 super(ProfileLocate, self).parse_command_line(argv)
127 127 if self.extra_args:
128 128 self.profile = self.extra_args[0]
129 129
130 130 def start(self):
131 131 print(self.profile_dir.location)
132 132
133 133
134 134 class ProfileList(Application):
135 135 name = u'ipython-profile'
136 136 description = list_help
137 137 examples = _list_examples
138 138
139 139 aliases = Dict({
140 140 'ipython-dir' : 'ProfileList.ipython_dir',
141 141 'log-level' : 'Application.log_level',
142 142 })
143 143 flags = Dict(dict(
144 144 debug = ({'Application' : {'log_level' : 0}},
145 145 "Set Application.log_level to 0, maximizing log output."
146 146 )
147 147 ))
148 148
149 149 ipython_dir = Unicode(get_ipython_dir(),
150 150 help="""
151 151 The name of the IPython directory. This directory is used for logging
152 152 configuration (through profiles), history storage, etc. The default
153 153 is usually $HOME/.ipython. This options can also be specified through
154 154 the environment variable IPYTHONDIR.
155 155 """
156 156 ).tag(config=True)
157 157
158 158
159 159 def _print_profiles(self, profiles):
160 160 """print list of profiles, indented."""
161 161 for profile in profiles:
162 162 print(' %s' % profile)
163 163
164 164 def list_profile_dirs(self):
165 165 profiles = list_bundled_profiles()
166 166 if profiles:
167 167 print()
168 168 print("Available profiles in IPython:")
169 169 self._print_profiles(profiles)
170 170 print()
171 171 print(" The first request for a bundled profile will copy it")
172 172 print(" into your IPython directory (%s)," % self.ipython_dir)
173 173 print(" where you can customize it.")
174 174
175 175 profiles = list_profiles_in(self.ipython_dir)
176 176 if profiles:
177 177 print()
178 178 print("Available profiles in %s:" % self.ipython_dir)
179 179 self._print_profiles(profiles)
180 180
181 181 profiles = list_profiles_in(os.getcwd())
182 182 if profiles:
183 183 print()
184 184 print(
185 185 "Profiles from CWD have been removed for security reason, see CVE-2022-21699:"
186 186 )
187 187
188 188 print()
189 189 print("To use any of the above profiles, start IPython with:")
190 190 print(" ipython --profile=<name>")
191 191 print()
192 192
193 193 def start(self):
194 194 self.list_profile_dirs()
195 195
196 196
197 197 create_flags = {}
198 198 create_flags.update(base_flags)
199 199 # don't include '--init' flag, which implies running profile create in other apps
200 200 create_flags.pop('init')
201 201 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
202 202 "reset config files in this profile to the defaults.")
203 203 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
204 204 "Include the config files for parallel "
205 205 "computing apps (ipengine, ipcontroller, etc.)")
206 206
207 207
208 208 class ProfileCreate(BaseIPythonApplication):
209 209 name = u'ipython-profile'
210 210 description = create_help
211 211 examples = _create_examples
212 auto_create = Bool(True)
212 auto_create = Bool(True).tag(config=True)
213 213 def _log_format_default(self):
214 214 return "[%(name)s] %(message)s"
215 215
216 216 def _copy_config_files_default(self):
217 217 return True
218 218
219 219 parallel = Bool(False,
220 220 help="whether to include parallel computing config files"
221 221 ).tag(config=True)
222 222
223 223 @observe('parallel')
224 224 def _parallel_changed(self, change):
225 225 parallel_files = [ 'ipcontroller_config.py',
226 226 'ipengine_config.py',
227 227 'ipcluster_config.py'
228 228 ]
229 229 if change['new']:
230 230 for cf in parallel_files:
231 231 self.config_files.append(cf)
232 232 else:
233 233 for cf in parallel_files:
234 234 if cf in self.config_files:
235 235 self.config_files.remove(cf)
236 236
237 237 def parse_command_line(self, argv):
238 238 super(ProfileCreate, self).parse_command_line(argv)
239 239 # accept positional arg as profile name
240 240 if self.extra_args:
241 241 self.profile = self.extra_args[0]
242 242
243 243 flags = Dict(create_flags)
244 244
245 245 classes = [ProfileDir]
246 246
247 247 def _import_app(self, app_path):
248 248 """import an app class"""
249 249 app = None
250 250 name = app_path.rsplit('.', 1)[-1]
251 251 try:
252 252 app = import_item(app_path)
253 253 except ImportError:
254 254 self.log.info("Couldn't import %s, config file will be excluded", name)
255 255 except Exception:
256 256 self.log.warning('Unexpected error importing %s', name, exc_info=True)
257 257 return app
258 258
259 259 def init_config_files(self):
260 260 super(ProfileCreate, self).init_config_files()
261 261 # use local imports, since these classes may import from here
262 262 from IPython.terminal.ipapp import TerminalIPythonApp
263 263 apps = [TerminalIPythonApp]
264 264 for app_path in (
265 265 'ipykernel.kernelapp.IPKernelApp',
266 266 ):
267 267 app = self._import_app(app_path)
268 268 if app is not None:
269 269 apps.append(app)
270 270 if self.parallel:
271 271 from ipyparallel.apps.ipcontrollerapp import IPControllerApp
272 272 from ipyparallel.apps.ipengineapp import IPEngineApp
273 273 from ipyparallel.apps.ipclusterapp import IPClusterStart
274 274 apps.extend([
275 275 IPControllerApp,
276 276 IPEngineApp,
277 277 IPClusterStart,
278 278 ])
279 279 for App in apps:
280 280 app = App()
281 281 app.config.update(self.config)
282 282 app.log = self.log
283 283 app.overwrite = self.overwrite
284 284 app.copy_config_files=True
285 285 app.ipython_dir=self.ipython_dir
286 286 app.profile_dir=self.profile_dir
287 287 app.init_config_files()
288 288
289 289 def stage_default_config_file(self):
290 290 pass
291 291
292 292
293 293 class ProfileApp(Application):
294 294 name = u'ipython profile'
295 295 description = profile_help
296 296 examples = _main_examples
297 297
298 298 subcommands = Dict(dict(
299 299 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
300 300 list = (ProfileList, ProfileList.description.splitlines()[0]),
301 301 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
302 302 ))
303 303
304 304 def start(self):
305 305 if self.subapp is None:
306 306 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
307 307 print()
308 308 self.print_description()
309 309 self.print_subcommands()
310 310 self.exit(1)
311 311 else:
312 312 return self.subapp.start()
@@ -1,338 +1,338 b''
1 1 # encoding: utf-8
2 2 """
3 3 The :class:`~traitlets.config.application.Application` object for the command
4 4 line :command:`ipython` program.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10
11 11 import logging
12 12 import os
13 13 import sys
14 14 import warnings
15 15
16 16 from traitlets.config.loader import Config
17 17 from traitlets.config.application import boolean_flag, catch_config_error
18 18 from IPython.core import release
19 19 from IPython.core import usage
20 20 from IPython.core.completer import IPCompleter
21 21 from IPython.core.crashhandler import CrashHandler
22 22 from IPython.core.formatters import PlainTextFormatter
23 23 from IPython.core.history import HistoryManager
24 24 from IPython.core.application import (
25 25 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
26 26 )
27 27 from IPython.core.magic import MagicsManager
28 28 from IPython.core.magics import (
29 29 ScriptMagics, LoggingMagics
30 30 )
31 31 from IPython.core.shellapp import (
32 32 InteractiveShellApp, shell_flags, shell_aliases
33 33 )
34 34 from IPython.extensions.storemagic import StoreMagics
35 35 from .interactiveshell import TerminalInteractiveShell
36 36 from IPython.paths import get_ipython_dir
37 37 from traitlets import (
38 38 Bool, List, default, observe, Type
39 39 )
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Globals, utilities and helpers
43 43 #-----------------------------------------------------------------------------
44 44
45 45 _examples = """
46 46 ipython --matplotlib # enable matplotlib integration
47 47 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
48 48
49 49 ipython --log-level=DEBUG # set logging to DEBUG
50 50 ipython --profile=foo # start with profile foo
51 51
52 52 ipython profile create foo # create profile foo w/ default config files
53 53 ipython help profile # show the help for the profile subcmd
54 54
55 55 ipython locate # print the path to the IPython directory
56 56 ipython locate profile foo # print the path to the directory for profile `foo`
57 57 """
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Crash handler for this application
61 61 #-----------------------------------------------------------------------------
62 62
63 63 class IPAppCrashHandler(CrashHandler):
64 64 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
65 65
66 66 def __init__(self, app):
67 67 contact_name = release.author
68 68 contact_email = release.author_email
69 69 bug_tracker = 'https://github.com/ipython/ipython/issues'
70 70 super(IPAppCrashHandler,self).__init__(
71 71 app, contact_name, contact_email, bug_tracker
72 72 )
73 73
74 74 def make_report(self,traceback):
75 75 """Return a string containing a crash report."""
76 76
77 77 sec_sep = self.section_sep
78 78 # Start with parent report
79 79 report = [super(IPAppCrashHandler, self).make_report(traceback)]
80 80 # Add interactive-specific info we may have
81 81 rpt_add = report.append
82 82 try:
83 83 rpt_add(sec_sep+"History of session input:")
84 84 for line in self.app.shell.user_ns['_ih']:
85 85 rpt_add(line)
86 86 rpt_add('\n*** Last line of input (may not be in above history):\n')
87 87 rpt_add(self.app.shell._last_input_line+'\n')
88 88 except:
89 89 pass
90 90
91 91 return ''.join(report)
92 92
93 93 #-----------------------------------------------------------------------------
94 94 # Aliases and Flags
95 95 #-----------------------------------------------------------------------------
96 96 flags = dict(base_flags)
97 97 flags.update(shell_flags)
98 98 frontend_flags = {}
99 99 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
100 100 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
101 101 'Turn on auto editing of files with syntax errors.',
102 102 'Turn off auto editing of files with syntax errors.'
103 103 )
104 104 addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt',
105 105 "Force simple minimal prompt using `raw_input`",
106 106 "Use a rich interactive prompt with prompt_toolkit",
107 107 )
108 108
109 109 addflag('banner', 'TerminalIPythonApp.display_banner',
110 110 "Display a banner upon starting IPython.",
111 111 "Don't display a banner upon starting IPython."
112 112 )
113 113 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
114 114 """Set to confirm when you try to exit IPython with an EOF (Control-D
115 115 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
116 116 you can force a direct exit without any confirmation.""",
117 117 "Don't prompt the user when exiting."
118 118 )
119 119 addflag('term-title', 'TerminalInteractiveShell.term_title',
120 120 "Enable auto setting the terminal title.",
121 121 "Disable auto setting the terminal title."
122 122 )
123 123 classic_config = Config()
124 124 classic_config.InteractiveShell.cache_size = 0
125 125 classic_config.PlainTextFormatter.pprint = False
126 126 classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts'
127 127 classic_config.InteractiveShell.separate_in = ''
128 128 classic_config.InteractiveShell.separate_out = ''
129 129 classic_config.InteractiveShell.separate_out2 = ''
130 130 classic_config.InteractiveShell.colors = 'NoColor'
131 131 classic_config.InteractiveShell.xmode = 'Plain'
132 132
133 133 frontend_flags['classic']=(
134 134 classic_config,
135 135 "Gives IPython a similar feel to the classic Python prompt."
136 136 )
137 137 # # log doesn't make so much sense this way anymore
138 138 # paa('--log','-l',
139 139 # action='store_true', dest='InteractiveShell.logstart',
140 140 # help="Start logging to the default log file (./ipython_log.py).")
141 141 #
142 142 # # quick is harder to implement
143 143 frontend_flags['quick']=(
144 144 {'TerminalIPythonApp' : {'quick' : True}},
145 145 "Enable quick startup with no config files."
146 146 )
147 147
148 148 frontend_flags['i'] = (
149 149 {'TerminalIPythonApp' : {'force_interact' : True}},
150 150 """If running code from the command line, become interactive afterwards.
151 151 It is often useful to follow this with `--` to treat remaining flags as
152 152 script arguments.
153 153 """
154 154 )
155 155 flags.update(frontend_flags)
156 156
157 157 aliases = dict(base_aliases)
158 158 aliases.update(shell_aliases) # type: ignore[arg-type]
159 159
160 160 #-----------------------------------------------------------------------------
161 161 # Main classes and functions
162 162 #-----------------------------------------------------------------------------
163 163
164 164
165 165 class LocateIPythonApp(BaseIPythonApplication):
166 166 description = """print the path to the IPython dir"""
167 167 subcommands = dict(
168 168 profile=('IPython.core.profileapp.ProfileLocate',
169 169 "print the path to an IPython profile directory",
170 170 ),
171 171 )
172 172 def start(self):
173 173 if self.subapp is not None:
174 174 return self.subapp.start()
175 175 else:
176 176 print(self.ipython_dir)
177 177
178 178
179 179 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
180 name = u'ipython'
180 name = "ipython"
181 181 description = usage.cl_usage
182 182 crash_handler_class = IPAppCrashHandler # typing: ignore[assignment]
183 183 examples = _examples
184 184
185 185 flags = flags
186 186 aliases = aliases
187 187 classes = List()
188 188
189 189 interactive_shell_class = Type(
190 190 klass=object, # use default_value otherwise which only allow subclasses.
191 191 default_value=TerminalInteractiveShell,
192 192 help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends"
193 193 ).tag(config=True)
194 194
195 195 @default('classes')
196 196 def _classes_default(self):
197 197 """This has to be in a method, for TerminalIPythonApp to be available."""
198 198 return [
199 InteractiveShellApp, # ShellApp comes before TerminalApp, because
199 InteractiveShellApp, # ShellApp comes before TerminalApp, because
200 200 self.__class__, # it will also affect subclasses (e.g. QtConsole)
201 201 TerminalInteractiveShell,
202 202 HistoryManager,
203 203 MagicsManager,
204 204 ProfileDir,
205 205 PlainTextFormatter,
206 206 IPCompleter,
207 207 ScriptMagics,
208 208 LoggingMagics,
209 209 StoreMagics,
210 210 ]
211 211
212 212 subcommands = dict(
213 213 profile = ("IPython.core.profileapp.ProfileApp",
214 214 "Create and manage IPython profiles."
215 215 ),
216 216 kernel = ("ipykernel.kernelapp.IPKernelApp",
217 217 "Start a kernel without an attached frontend."
218 218 ),
219 219 locate=('IPython.terminal.ipapp.LocateIPythonApp',
220 220 LocateIPythonApp.description
221 221 ),
222 222 history=('IPython.core.historyapp.HistoryApp',
223 223 "Manage the IPython history database."
224 224 ),
225 225 )
226 226
227
228 227 # *do* autocreate requested profile, but don't create the config file.
229 auto_create=Bool(True)
228 auto_create = Bool(True).tag(config=True)
229
230 230 # configurables
231 231 quick = Bool(False,
232 232 help="""Start IPython quickly by skipping the loading of config files."""
233 233 ).tag(config=True)
234 234 @observe('quick')
235 235 def _quick_changed(self, change):
236 236 if change['new']:
237 237 self.load_config_file = lambda *a, **kw: None
238 238
239 239 display_banner = Bool(True,
240 240 help="Whether to display a banner upon starting IPython."
241 241 ).tag(config=True)
242 242
243 243 # if there is code of files to run from the cmd line, don't interact
244 244 # unless the --i flag (App.force_interact) is true.
245 245 force_interact = Bool(False,
246 246 help="""If a command or file is given via the command-line,
247 247 e.g. 'ipython foo.py', start an interactive shell after executing the
248 248 file or command."""
249 249 ).tag(config=True)
250 250 @observe('force_interact')
251 251 def _force_interact_changed(self, change):
252 252 if change['new']:
253 253 self.interact = True
254 254
255 255 @observe('file_to_run', 'code_to_run', 'module_to_run')
256 256 def _file_to_run_changed(self, change):
257 257 new = change['new']
258 258 if new:
259 259 self.something_to_run = True
260 260 if new and not self.force_interact:
261 261 self.interact = False
262 262
263 263 # internal, not-configurable
264 264 something_to_run=Bool(False)
265 265
266 266 @catch_config_error
267 267 def initialize(self, argv=None):
268 268 """Do actions after construct, but before starting the app."""
269 269 super(TerminalIPythonApp, self).initialize(argv)
270 270 if self.subapp is not None:
271 271 # don't bother initializing further, starting subapp
272 272 return
273 273 # print self.extra_args
274 274 if self.extra_args and not self.something_to_run:
275 275 self.file_to_run = self.extra_args[0]
276 276 self.init_path()
277 277 # create the shell
278 278 self.init_shell()
279 279 # and draw the banner
280 280 self.init_banner()
281 281 # Now a variety of things that happen after the banner is printed.
282 282 self.init_gui_pylab()
283 283 self.init_extensions()
284 284 self.init_code()
285 285
286 286 def init_shell(self):
287 287 """initialize the InteractiveShell instance"""
288 288 # Create an InteractiveShell instance.
289 289 # shell.display_banner should always be False for the terminal
290 290 # based app, because we call shell.show_banner() by hand below
291 291 # so the banner shows *before* all extension loading stuff.
292 292 self.shell = self.interactive_shell_class.instance(parent=self,
293 293 profile_dir=self.profile_dir,
294 294 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
295 295 self.shell.configurables.append(self)
296 296
297 297 def init_banner(self):
298 298 """optionally display the banner"""
299 299 if self.display_banner and self.interact:
300 300 self.shell.show_banner()
301 301 # Make sure there is a space below the banner.
302 302 if self.log_level <= logging.INFO: print()
303 303
304 304 def _pylab_changed(self, name, old, new):
305 305 """Replace --pylab='inline' with --pylab='auto'"""
306 306 if new == 'inline':
307 307 warnings.warn("'inline' not available as pylab backend, "
308 308 "using 'auto' instead.")
309 309 self.pylab = 'auto'
310 310
311 311 def start(self):
312 312 if self.subapp is not None:
313 313 return self.subapp.start()
314 314 # perform any prexec steps:
315 315 if self.interact:
316 316 self.log.debug("Starting IPython's mainloop...")
317 317 self.shell.mainloop()
318 318 else:
319 319 self.log.debug("IPython not interactive...")
320 320 self.shell.restore_term_title()
321 321 if not self.shell.last_execution_succeeded:
322 322 sys.exit(1)
323 323
324 324 def load_default_config(ipython_dir=None):
325 325 """Load the default config file from the default ipython_dir.
326 326
327 327 This is useful for embedded shells.
328 328 """
329 329 if ipython_dir is None:
330 330 ipython_dir = get_ipython_dir()
331 331
332 332 profile_dir = os.path.join(ipython_dir, 'profile_default')
333 333 app = TerminalIPythonApp()
334 334 app.config_file_paths.append(profile_dir)
335 335 app.load_config_file()
336 336 return app.config
337 337
338 338 launch_new_instance = TerminalIPythonApp.launch_instance
General Comments 0
You need to be logged in to leave comments. Login now