##// END OF EJS Templates
Merge pull request from GHSA-pq7m-3gw7-gq5x...
Matthias Bussonnier -
Show More
@@ -0,0 +1,56 b''
1 """
2 Test that CVEs stay fixed.
3 """
4
5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
6 from pathlib import Path
7 import random
8 import sys
9 import os
10 import string
11 import subprocess
12 import time
13
14 def test_cve_2022_21699():
15 """
16 Here we test CVE-2022-21699.
17
18 We create a temporary directory, cd into it.
19 Make a profile file that should not be executed and start IPython in a subprocess,
20 checking for the value.
21
22
23
24 """
25
26 dangerous_profile_dir = Path('profile_default')
27
28 dangerous_startup_dir = dangerous_profile_dir / 'startup'
29 dangerous_expected = 'CVE-2022-21699-'+''.join([random.choice(string.ascii_letters) for i in range(10)])
30
31 with TemporaryWorkingDirectory() as t:
32 dangerous_startup_dir.mkdir(parents=True)
33 (dangerous_startup_dir/ 'foo.py').write_text(f'print("{dangerous_expected}")')
34 # 1 sec to make sure FS is flushed.
35 #time.sleep(1)
36 cmd = [sys.executable,'-m', 'IPython']
37 env = os.environ.copy()
38 env['IPY_TEST_SIMPLE_PROMPT'] = '1'
39
40
41 # First we fake old behavior, making sure the profile is/was actually dangerous
42 p_dangerous = subprocess.Popen(cmd + [f'--profile-dir={dangerous_profile_dir}'], env=env, stdin=subprocess.PIPE,
43 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
44 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
45 assert dangerous_expected in out_dangerous.decode()
46
47 # Now that we know it _would_ have been dangerous, we test it's not loaded
48 p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE,
49 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
50 out, err = p.communicate(b"exit\r")
51 assert b'IPython' in out
52 assert dangerous_expected not in out.decode()
53 assert err == b''
54
55
56
@@ -1,151 +1,155 b''
1 1 """
2 2 IPython: tools for interactive and parallel computing in Python.
3 3
4 4 https://ipython.org
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2008-2011, IPython Development Team.
8 8 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
9 9 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 10 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 11 #
12 12 # Distributed under the terms of the Modified BSD License.
13 13 #
14 14 # The full license is in the file COPYING.txt, distributed with this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 22 import sys
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Setup everything
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Don't forget to also update setup.py when this changes!
29 29 if sys.version_info < (3, 8):
30 30 raise ImportError(
31 31 """
32 32 IPython 8+ supports Python 3.8 and above, following NEP 29.
33 33 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 34 Python 3.3 and 3.4 were supported up to IPython 6.x.
35 35 Python 3.5 was supported with IPython 7.0 to 7.9.
36 36 Python 3.6 was supported with IPython up to 7.16.
37 37 Python 3.7 was still supported with the 7.x branch.
38 38
39 39 See IPython `README.rst` file for more information:
40 40
41 41 https://github.com/ipython/ipython/blob/master/README.rst
42 42
43 43 """)
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Setup the top level names
47 47 #-----------------------------------------------------------------------------
48 48
49 49 from .core.getipython import get_ipython
50 50 from .core import release
51 51 from .core.application import Application
52 52 from .terminal.embed import embed
53 53
54 54 from .core.interactiveshell import InteractiveShell
55 55 from .utils.sysinfo import sys_info
56 56 from .utils.frame import extract_module_locals
57 57
58 58 # Release data
59 59 __author__ = '%s <%s>' % (release.author, release.author_email)
60 60 __license__ = release.license
61 61 __version__ = release.version
62 62 version_info = release.version_info
63 # list of CVEs that should have been patched in this release.
64 # this is informational and should not be relied upon.
65 __patched_cves__ = {"CVE-2022-21699"}
66
63 67
64 68 def embed_kernel(module=None, local_ns=None, **kwargs):
65 69 """Embed and start an IPython kernel in a given scope.
66 70
67 71 If you don't want the kernel to initialize the namespace
68 72 from the scope of the surrounding function,
69 73 and/or you want to load full IPython configuration,
70 74 you probably want `IPython.start_kernel()` instead.
71 75
72 76 Parameters
73 77 ----------
74 78 module : types.ModuleType, optional
75 79 The module to load into IPython globals (default: caller)
76 80 local_ns : dict, optional
77 81 The namespace to load into IPython user namespace (default: caller)
78 82 **kwargs : various, optional
79 83 Further keyword args are relayed to the IPKernelApp constructor,
80 84 allowing configuration of the Kernel. Will only have an effect
81 85 on the first embed_kernel call for a given process.
82 86 """
83 87
84 88 (caller_module, caller_locals) = extract_module_locals(1)
85 89 if module is None:
86 90 module = caller_module
87 91 if local_ns is None:
88 92 local_ns = caller_locals
89 93
90 94 # Only import .zmq when we really need it
91 95 from ipykernel.embed import embed_kernel as real_embed_kernel
92 96 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
93 97
94 98 def start_ipython(argv=None, **kwargs):
95 99 """Launch a normal IPython instance (as opposed to embedded)
96 100
97 101 `IPython.embed()` puts a shell in a particular calling scope,
98 102 such as a function or method for debugging purposes,
99 103 which is often not desirable.
100 104
101 105 `start_ipython()` does full, regular IPython initialization,
102 106 including loading startup files, configuration, etc.
103 107 much of which is skipped by `embed()`.
104 108
105 109 This is a public API method, and will survive implementation changes.
106 110
107 111 Parameters
108 112 ----------
109 113 argv : list or None, optional
110 114 If unspecified or None, IPython will parse command-line options from sys.argv.
111 115 To prevent any command-line parsing, pass an empty list: `argv=[]`.
112 116 user_ns : dict, optional
113 117 specify this dictionary to initialize the IPython user namespace with particular values.
114 118 **kwargs : various, optional
115 119 Any other kwargs will be passed to the Application constructor,
116 120 such as `config`.
117 121 """
118 122 from IPython.terminal.ipapp import launch_new_instance
119 123 return launch_new_instance(argv=argv, **kwargs)
120 124
121 125 def start_kernel(argv=None, **kwargs):
122 126 """Launch a normal IPython kernel instance (as opposed to embedded)
123 127
124 128 `IPython.embed_kernel()` puts a shell in a particular calling scope,
125 129 such as a function or method for debugging purposes,
126 130 which is often not desirable.
127 131
128 132 `start_kernel()` does full, regular IPython initialization,
129 133 including loading startup files, configuration, etc.
130 134 much of which is skipped by `embed()`.
131 135
132 136 Parameters
133 137 ----------
134 138 argv : list or None, optional
135 139 If unspecified or None, IPython will parse command-line options from sys.argv.
136 140 To prevent any command-line parsing, pass an empty list: `argv=[]`.
137 141 user_ns : dict, optional
138 142 specify this dictionary to initialize the IPython user namespace with particular values.
139 143 **kwargs : various, optional
140 144 Any other kwargs will be passed to the Application constructor,
141 145 such as `config`.
142 146 """
143 147 import warnings
144 148
145 149 warnings.warn(
146 150 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
147 151 DeprecationWarning,
148 152 stacklevel=2,
149 153 )
150 154 from ipykernel.kernelapp import launch_new_instance
151 155 return launch_new_instance(argv=argv, **kwargs)
@@ -1,490 +1,490 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 = os.environ.get("PROGRAMDATA", None)
38 38 if programdata is not None:
39 39 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
40 40 else: # PROGRAMDATA is not defined by default on XP.
41 41 SYSTEM_CONFIG_DIRS = []
42 42 else:
43 43 SYSTEM_CONFIG_DIRS = [
44 44 "/usr/local/etc/ipython",
45 45 "/etc/ipython",
46 46 ]
47 47
48 48
49 49 ENV_CONFIG_DIRS = []
50 50 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
51 51 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
52 52 # only add ENV_CONFIG if sys.prefix is not already included
53 53 ENV_CONFIG_DIRS.append(_env_config_dir)
54 54
55 55
56 56 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
57 57 if _envvar in {None, ''}:
58 58 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
59 59 else:
60 60 if _envvar.lower() in {'1','true'}:
61 61 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
62 62 elif _envvar.lower() in {'0','false'} :
63 63 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
64 64 else:
65 65 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
66 66
67 67 # aliases and flags
68 68
69 69 base_aliases = {}
70 70 if isinstance(Application.aliases, dict):
71 71 # traitlets 5
72 72 base_aliases.update(Application.aliases)
73 73 base_aliases.update(
74 74 {
75 75 "profile-dir": "ProfileDir.location",
76 76 "profile": "BaseIPythonApplication.profile",
77 77 "ipython-dir": "BaseIPythonApplication.ipython_dir",
78 78 "log-level": "Application.log_level",
79 79 "config": "BaseIPythonApplication.extra_config_file",
80 80 }
81 81 )
82 82
83 83 base_flags = dict()
84 84 if isinstance(Application.flags, dict):
85 85 # traitlets 5
86 86 base_flags.update(Application.flags)
87 87 base_flags.update(
88 88 dict(
89 89 debug=(
90 90 {"Application": {"log_level": logging.DEBUG}},
91 91 "set log level to logging.DEBUG (maximize logging output)",
92 92 ),
93 93 quiet=(
94 94 {"Application": {"log_level": logging.CRITICAL}},
95 95 "set log level to logging.CRITICAL (minimize logging output)",
96 96 ),
97 97 init=(
98 98 {
99 99 "BaseIPythonApplication": {
100 100 "copy_config_files": True,
101 101 "auto_create": True,
102 102 }
103 103 },
104 104 """Initialize profile with default config files. This is equivalent
105 105 to running `ipython profile create <profile>` prior to startup.
106 106 """,
107 107 ),
108 108 )
109 109 )
110 110
111 111
112 112 class ProfileAwareConfigLoader(PyFileConfigLoader):
113 113 """A Python file config loader that is aware of IPython profiles."""
114 114 def load_subconfig(self, fname, path=None, profile=None):
115 115 if profile is not None:
116 116 try:
117 117 profile_dir = ProfileDir.find_profile_dir_by_name(
118 118 get_ipython_dir(),
119 119 profile,
120 120 )
121 121 except ProfileDirError:
122 122 return
123 123 path = profile_dir.location
124 124 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
125 125
126 126 class BaseIPythonApplication(Application):
127 127
128 128 name = u'ipython'
129 129 description = Unicode(u'IPython: an enhanced interactive Python shell.')
130 130 version = Unicode(release.version)
131 131
132 132 aliases = base_aliases
133 133 flags = base_flags
134 134 classes = List([ProfileDir])
135 135
136 136 # enable `load_subconfig('cfg.py', profile='name')`
137 137 python_config_loader_class = ProfileAwareConfigLoader
138 138
139 139 # Track whether the config_file has changed,
140 140 # because some logic happens only if we aren't using the default.
141 141 config_file_specified = Set()
142 142
143 143 config_file_name = Unicode()
144 144 @default('config_file_name')
145 145 def _config_file_name_default(self):
146 146 return self.name.replace('-','_') + u'_config.py'
147 147 @observe('config_file_name')
148 148 def _config_file_name_changed(self, change):
149 149 if change['new'] != change['old']:
150 150 self.config_file_specified.add(change['new'])
151 151
152 152 # The directory that contains IPython's builtin profiles.
153 153 builtin_profile_dir = Unicode(
154 154 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
155 155 )
156 156
157 157 config_file_paths = List(Unicode())
158 158 @default('config_file_paths')
159 159 def _config_file_paths_default(self):
160 return [os.getcwd()]
160 return []
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 add_ipython_dir_to_sys_path = Bool(
189 189 False,
190 190 """Should the IPython profile directory be added to sys path ?
191 191
192 192 This option was non-existing before IPython 8.0, and ipython_dir was added to
193 193 sys path to allow import of extensions present there. This was historical
194 194 baggage from when pip did not exist. This now default to false,
195 195 but can be set to true for legacy reasons.
196 196 """,
197 197 ).tag(config=True)
198 198
199 199 ipython_dir = Unicode(
200 200 help="""
201 201 The name of the IPython directory. This directory is used for logging
202 202 configuration (through profiles), history storage, etc. The default
203 203 is usually $HOME/.ipython. This option can also be specified through
204 204 the environment variable IPYTHONDIR.
205 205 """
206 206 ).tag(config=True)
207 207 @default('ipython_dir')
208 208 def _ipython_dir_default(self):
209 209 d = get_ipython_dir()
210 210 self._ipython_dir_changed({
211 211 'name': 'ipython_dir',
212 212 'old': d,
213 213 'new': d,
214 214 })
215 215 return d
216 216
217 217 _in_init_profile_dir = False
218 218 profile_dir = Instance(ProfileDir, allow_none=True)
219 219 @default('profile_dir')
220 220 def _profile_dir_default(self):
221 221 # avoid recursion
222 222 if self._in_init_profile_dir:
223 223 return
224 224 # profile_dir requested early, force initialization
225 225 self.init_profile_dir()
226 226 return self.profile_dir
227 227
228 228 overwrite = Bool(False,
229 229 help="""Whether to overwrite existing config files when copying"""
230 230 ).tag(config=True)
231 231 auto_create = Bool(False,
232 232 help="""Whether to create profile dir if it doesn't exist"""
233 233 ).tag(config=True)
234 234
235 235 config_files = List(Unicode())
236 236 @default('config_files')
237 237 def _config_files_default(self):
238 238 return [self.config_file_name]
239 239
240 240 copy_config_files = Bool(False,
241 241 help="""Whether to install the default config files into the profile dir.
242 242 If a new profile is being created, and IPython contains config files for that
243 243 profile, then they will be staged into the new directory. Otherwise,
244 244 default config files will be automatically generated.
245 245 """).tag(config=True)
246 246
247 247 verbose_crash = Bool(False,
248 248 help="""Create a massive crash report when IPython encounters what may be an
249 249 internal error. The default is to append a short message to the
250 250 usual traceback""").tag(config=True)
251 251
252 252 # The class to use as the crash handler.
253 253 crash_handler_class = Type(crashhandler.CrashHandler)
254 254
255 255 @catch_config_error
256 256 def __init__(self, **kwargs):
257 257 super(BaseIPythonApplication, self).__init__(**kwargs)
258 258 # ensure current working directory exists
259 259 try:
260 260 os.getcwd()
261 261 except:
262 262 # exit if cwd doesn't exist
263 263 self.log.error("Current working directory doesn't exist.")
264 264 self.exit(1)
265 265
266 266 #-------------------------------------------------------------------------
267 267 # Various stages of Application creation
268 268 #-------------------------------------------------------------------------
269 269
270 270 def init_crash_handler(self):
271 271 """Create a crash handler, typically setting sys.excepthook to it."""
272 272 self.crash_handler = self.crash_handler_class(self)
273 273 sys.excepthook = self.excepthook
274 274 def unset_crashhandler():
275 275 sys.excepthook = sys.__excepthook__
276 276 atexit.register(unset_crashhandler)
277 277
278 278 def excepthook(self, etype, evalue, tb):
279 279 """this is sys.excepthook after init_crashhandler
280 280
281 281 set self.verbose_crash=True to use our full crashhandler, instead of
282 282 a regular traceback with a short message (crash_handler_lite)
283 283 """
284 284
285 285 if self.verbose_crash:
286 286 return self.crash_handler(etype, evalue, tb)
287 287 else:
288 288 return crashhandler.crash_handler_lite(etype, evalue, tb)
289 289
290 290 @observe('ipython_dir')
291 291 def _ipython_dir_changed(self, change):
292 292 old = change['old']
293 293 new = change['new']
294 294 if old is not Undefined:
295 295 str_old = os.path.abspath(old)
296 296 if str_old in sys.path:
297 297 sys.path.remove(str_old)
298 298 if self.add_ipython_dir_to_sys_path:
299 299 str_path = os.path.abspath(new)
300 300 sys.path.append(str_path)
301 301 ensure_dir_exists(new)
302 302 readme = os.path.join(new, "README")
303 303 readme_src = os.path.join(
304 304 get_ipython_package_dir(), "config", "profile", "README"
305 305 )
306 306 if not os.path.exists(readme) and os.path.exists(readme_src):
307 307 shutil.copy(readme_src, readme)
308 308 for d in ("extensions", "nbextensions"):
309 309 path = os.path.join(new, d)
310 310 try:
311 311 ensure_dir_exists(path)
312 312 except OSError as e:
313 313 # this will not be EEXIST
314 314 self.log.error("couldn't create path %s: %s", path, e)
315 315 self.log.debug("IPYTHONDIR set to: %s" % new)
316 316
317 317 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
318 318 """Load the config file.
319 319
320 320 By default, errors in loading config are handled, and a warning
321 321 printed on screen. For testing, the suppress_errors option is set
322 322 to False, so errors will make tests fail.
323 323
324 324 `suppress_errors` default value is to be `None` in which case the
325 325 behavior default to the one of `traitlets.Application`.
326 326
327 327 The default value can be set :
328 328 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
329 329 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
330 330 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
331 331
332 332 Any other value are invalid, and will make IPython exit with a non-zero return code.
333 333 """
334 334
335 335
336 336 self.log.debug("Searching path %s for config files", self.config_file_paths)
337 337 base_config = 'ipython_config.py'
338 338 self.log.debug("Attempting to load config file: %s" %
339 339 base_config)
340 340 try:
341 341 if suppress_errors is not None:
342 342 old_value = Application.raise_config_file_errors
343 343 Application.raise_config_file_errors = not suppress_errors;
344 344 Application.load_config_file(
345 345 self,
346 346 base_config,
347 347 path=self.config_file_paths
348 348 )
349 349 except ConfigFileNotFound:
350 350 # ignore errors loading parent
351 351 self.log.debug("Config file %s not found", base_config)
352 352 pass
353 353 if suppress_errors is not None:
354 354 Application.raise_config_file_errors = old_value
355 355
356 356 for config_file_name in self.config_files:
357 357 if not config_file_name or config_file_name == base_config:
358 358 continue
359 359 self.log.debug("Attempting to load config file: %s" %
360 360 self.config_file_name)
361 361 try:
362 362 Application.load_config_file(
363 363 self,
364 364 config_file_name,
365 365 path=self.config_file_paths
366 366 )
367 367 except ConfigFileNotFound:
368 368 # Only warn if the default config file was NOT being used.
369 369 if config_file_name in self.config_file_specified:
370 370 msg = self.log.warning
371 371 else:
372 372 msg = self.log.debug
373 373 msg("Config file not found, skipping: %s", config_file_name)
374 374 except Exception:
375 375 # For testing purposes.
376 376 if not suppress_errors:
377 377 raise
378 378 self.log.warning("Error loading config file: %s" %
379 379 self.config_file_name, exc_info=True)
380 380
381 381 def init_profile_dir(self):
382 382 """initialize the profile dir"""
383 383 self._in_init_profile_dir = True
384 384 if self.profile_dir is not None:
385 385 # already ran
386 386 return
387 387 if 'ProfileDir.location' not in self.config:
388 388 # location not specified, find by profile name
389 389 try:
390 390 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
391 391 except ProfileDirError:
392 392 # not found, maybe create it (always create default profile)
393 393 if self.auto_create or self.profile == 'default':
394 394 try:
395 395 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
396 396 except ProfileDirError:
397 397 self.log.fatal("Could not create profile: %r"%self.profile)
398 398 self.exit(1)
399 399 else:
400 400 self.log.info("Created profile dir: %r"%p.location)
401 401 else:
402 402 self.log.fatal("Profile %r not found."%self.profile)
403 403 self.exit(1)
404 404 else:
405 405 self.log.debug(f"Using existing profile dir: {p.location!r}")
406 406 else:
407 407 location = self.config.ProfileDir.location
408 408 # location is fully specified
409 409 try:
410 410 p = ProfileDir.find_profile_dir(location, self.config)
411 411 except ProfileDirError:
412 412 # not found, maybe create it
413 413 if self.auto_create:
414 414 try:
415 415 p = ProfileDir.create_profile_dir(location, self.config)
416 416 except ProfileDirError:
417 417 self.log.fatal("Could not create profile directory: %r"%location)
418 418 self.exit(1)
419 419 else:
420 420 self.log.debug("Creating new profile dir: %r"%location)
421 421 else:
422 422 self.log.fatal("Profile directory %r not found."%location)
423 423 self.exit(1)
424 424 else:
425 425 self.log.debug(f"Using existing profile dir: {p.location!r}")
426 426 # if profile_dir is specified explicitly, set profile name
427 427 dir_name = os.path.basename(p.location)
428 428 if dir_name.startswith('profile_'):
429 429 self.profile = dir_name[8:]
430 430
431 431 self.profile_dir = p
432 432 self.config_file_paths.append(p.location)
433 433 self._in_init_profile_dir = False
434 434
435 435 def init_config_files(self):
436 436 """[optionally] copy default config files into profile dir."""
437 437 self.config_file_paths.extend(ENV_CONFIG_DIRS)
438 438 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
439 439 # copy config files
440 440 path = Path(self.builtin_profile_dir)
441 441 if self.copy_config_files:
442 442 src = self.profile
443 443
444 444 cfg = self.config_file_name
445 445 if path and (path / cfg).exists():
446 446 self.log.warning(
447 447 "Staging %r from %s into %r [overwrite=%s]"
448 448 % (cfg, src, self.profile_dir.location, self.overwrite)
449 449 )
450 450 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
451 451 else:
452 452 self.stage_default_config_file()
453 453 else:
454 454 # Still stage *bundled* config files, but not generated ones
455 455 # This is necessary for `ipython profile=sympy` to load the profile
456 456 # on the first go
457 457 files = path.glob("*.py")
458 458 for fullpath in files:
459 459 cfg = fullpath.name
460 460 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
461 461 # file was copied
462 462 self.log.warning("Staging bundled %s from %s into %r"%(
463 463 cfg, self.profile, self.profile_dir.location)
464 464 )
465 465
466 466
467 467 def stage_default_config_file(self):
468 468 """auto generate default config file, and stage it into the profile."""
469 469 s = self.generate_config_file()
470 470 config_file = Path(self.profile_dir.location) / self.config_file_name
471 471 if self.overwrite or not config_file.exists():
472 472 self.log.warning("Generating default config file: %r" % (config_file))
473 473 config_file.write_text(s)
474 474
475 475 @catch_config_error
476 476 def initialize(self, argv=None):
477 477 # don't hook up crash handler before parsing command-line
478 478 self.parse_command_line(argv)
479 479 self.init_crash_handler()
480 480 if self.subapp is not None:
481 481 # stop here if subapp is taking over
482 482 return
483 483 # save a copy of CLI config to re-load after config files
484 484 # so that it has highest priority
485 485 cl_config = deepcopy(self.config)
486 486 self.init_profile_dir()
487 487 self.init_config_files()
488 488 self.load_config_file()
489 489 # enforce cl-opts override configfile opts:
490 490 self.update_config(cl_config)
@@ -1,311 +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 print("Available profiles in current directory (%s):" % os.getcwd())
185 self._print_profiles(profiles)
186
184 print(
185 "Profiles from CWD have been removed for security reason, see CVE-2022-21699:"
186 )
187
187 188 print()
188 189 print("To use any of the above profiles, start IPython with:")
189 190 print(" ipython --profile=<name>")
190 191 print()
191 192
192 193 def start(self):
193 194 self.list_profile_dirs()
194 195
195 196
196 197 create_flags = {}
197 198 create_flags.update(base_flags)
198 199 # don't include '--init' flag, which implies running profile create in other apps
199 200 create_flags.pop('init')
200 201 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
201 202 "reset config files in this profile to the defaults.")
202 203 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
203 204 "Include the config files for parallel "
204 205 "computing apps (ipengine, ipcontroller, etc.)")
205 206
206 207
207 208 class ProfileCreate(BaseIPythonApplication):
208 209 name = u'ipython-profile'
209 210 description = create_help
210 211 examples = _create_examples
211 212 auto_create = Bool(True)
212 213 def _log_format_default(self):
213 214 return "[%(name)s] %(message)s"
214 215
215 216 def _copy_config_files_default(self):
216 217 return True
217 218
218 219 parallel = Bool(False,
219 220 help="whether to include parallel computing config files"
220 221 ).tag(config=True)
221 222
222 223 @observe('parallel')
223 224 def _parallel_changed(self, change):
224 225 parallel_files = [ 'ipcontroller_config.py',
225 226 'ipengine_config.py',
226 227 'ipcluster_config.py'
227 228 ]
228 229 if change['new']:
229 230 for cf in parallel_files:
230 231 self.config_files.append(cf)
231 232 else:
232 233 for cf in parallel_files:
233 234 if cf in self.config_files:
234 235 self.config_files.remove(cf)
235 236
236 237 def parse_command_line(self, argv):
237 238 super(ProfileCreate, self).parse_command_line(argv)
238 239 # accept positional arg as profile name
239 240 if self.extra_args:
240 241 self.profile = self.extra_args[0]
241 242
242 243 flags = Dict(create_flags)
243 244
244 245 classes = [ProfileDir]
245 246
246 247 def _import_app(self, app_path):
247 248 """import an app class"""
248 249 app = None
249 250 name = app_path.rsplit('.', 1)[-1]
250 251 try:
251 252 app = import_item(app_path)
252 253 except ImportError:
253 254 self.log.info("Couldn't import %s, config file will be excluded", name)
254 255 except Exception:
255 256 self.log.warning('Unexpected error importing %s', name, exc_info=True)
256 257 return app
257 258
258 259 def init_config_files(self):
259 260 super(ProfileCreate, self).init_config_files()
260 261 # use local imports, since these classes may import from here
261 262 from IPython.terminal.ipapp import TerminalIPythonApp
262 263 apps = [TerminalIPythonApp]
263 264 for app_path in (
264 265 'ipykernel.kernelapp.IPKernelApp',
265 266 ):
266 267 app = self._import_app(app_path)
267 268 if app is not None:
268 269 apps.append(app)
269 270 if self.parallel:
270 271 from ipyparallel.apps.ipcontrollerapp import IPControllerApp
271 272 from ipyparallel.apps.ipengineapp import IPEngineApp
272 273 from ipyparallel.apps.ipclusterapp import IPClusterStart
273 274 apps.extend([
274 275 IPControllerApp,
275 276 IPEngineApp,
276 277 IPClusterStart,
277 278 ])
278 279 for App in apps:
279 280 app = App()
280 281 app.config.update(self.config)
281 282 app.log = self.log
282 283 app.overwrite = self.overwrite
283 284 app.copy_config_files=True
284 285 app.ipython_dir=self.ipython_dir
285 286 app.profile_dir=self.profile_dir
286 287 app.init_config_files()
287 288
288 289 def stage_default_config_file(self):
289 290 pass
290 291
291 292
292 293 class ProfileApp(Application):
293 294 name = u'ipython profile'
294 295 description = profile_help
295 296 examples = _main_examples
296 297
297 298 subcommands = Dict(dict(
298 299 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
299 300 list = (ProfileList, ProfileList.description.splitlines()[0]),
300 301 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
301 302 ))
302 303
303 304 def start(self):
304 305 if self.subapp is None:
305 306 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
306 307 print()
307 308 self.print_description()
308 309 self.print_subcommands()
309 310 self.exit(1)
310 311 else:
311 312 return self.subapp.start()
@@ -1,225 +1,225 b''
1 1 # encoding: utf-8
2 2 """An object for managing IPython profile directories."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import shutil
9 9 import errno
10 10 from pathlib import Path
11 11
12 12 from traitlets.config.configurable import LoggingConfigurable
13 13 from ..paths import get_ipython_package_dir
14 14 from ..utils.path import expand_path, ensure_dir_exists
15 15 from traitlets import Unicode, Bool, observe
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Module errors
19 19 #-----------------------------------------------------------------------------
20 20
21 21 class ProfileDirError(Exception):
22 22 pass
23 23
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Class for managing profile directories
27 27 #-----------------------------------------------------------------------------
28 28
29 29 class ProfileDir(LoggingConfigurable):
30 30 """An object to manage the profile directory and its resources.
31 31
32 32 The profile directory is used by all IPython applications, to manage
33 33 configuration, logging and security.
34 34
35 35 This object knows how to find, create and manage these directories. This
36 36 should be used by any code that wants to handle profiles.
37 37 """
38 38
39 39 security_dir_name = Unicode('security')
40 40 log_dir_name = Unicode('log')
41 41 startup_dir_name = Unicode('startup')
42 42 pid_dir_name = Unicode('pid')
43 43 static_dir_name = Unicode('static')
44 44 security_dir = Unicode(u'')
45 45 log_dir = Unicode(u'')
46 46 startup_dir = Unicode(u'')
47 47 pid_dir = Unicode(u'')
48 48 static_dir = Unicode(u'')
49 49
50 50 location = Unicode(u'',
51 51 help="""Set the profile location directly. This overrides the logic used by the
52 52 `profile` option.""",
53 53 ).tag(config=True)
54 54
55 55 _location_isset = Bool(False) # flag for detecting multiply set location
56 56 @observe('location')
57 57 def _location_changed(self, change):
58 58 if self._location_isset:
59 59 raise RuntimeError("Cannot set profile location more than once.")
60 60 self._location_isset = True
61 61 new = change['new']
62 62 ensure_dir_exists(new)
63 63
64 64 # ensure config files exist:
65 65 self.security_dir = os.path.join(new, self.security_dir_name)
66 66 self.log_dir = os.path.join(new, self.log_dir_name)
67 67 self.startup_dir = os.path.join(new, self.startup_dir_name)
68 68 self.pid_dir = os.path.join(new, self.pid_dir_name)
69 69 self.static_dir = os.path.join(new, self.static_dir_name)
70 70 self.check_dirs()
71 71
72 72 def _mkdir(self, path, mode=None):
73 73 """ensure a directory exists at a given path
74 74
75 75 This is a version of os.mkdir, with the following differences:
76 76
77 77 - returns True if it created the directory, False otherwise
78 78 - ignores EEXIST, protecting against race conditions where
79 79 the dir may have been created in between the check and
80 80 the creation
81 81 - sets permissions if requested and the dir already exists
82 82 """
83 83 if os.path.exists(path):
84 84 if mode and os.stat(path).st_mode != mode:
85 85 try:
86 86 os.chmod(path, mode)
87 87 except OSError:
88 88 self.log.warning(
89 89 "Could not set permissions on %s",
90 90 path
91 91 )
92 92 return False
93 93 try:
94 94 if mode:
95 95 os.mkdir(path, mode)
96 96 else:
97 97 os.mkdir(path)
98 98 except OSError as e:
99 99 if e.errno == errno.EEXIST:
100 100 return False
101 101 else:
102 102 raise
103 103
104 104 return True
105 105
106 106 @observe('log_dir')
107 107 def check_log_dir(self, change=None):
108 108 self._mkdir(self.log_dir)
109 109
110 110 @observe('startup_dir')
111 111 def check_startup_dir(self, change=None):
112 112 self._mkdir(self.startup_dir)
113 113
114 114 readme = os.path.join(self.startup_dir, 'README')
115 115 src = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'README_STARTUP')
116 116
117 117 if not os.path.exists(src):
118 118 self.log.warning("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
119 119
120 120 if os.path.exists(src) and not os.path.exists(readme):
121 121 shutil.copy(src, readme)
122 122
123 123 @observe('security_dir')
124 124 def check_security_dir(self, change=None):
125 125 self._mkdir(self.security_dir, 0o40700)
126 126
127 127 @observe('pid_dir')
128 128 def check_pid_dir(self, change=None):
129 129 self._mkdir(self.pid_dir, 0o40700)
130 130
131 131 def check_dirs(self):
132 132 self.check_security_dir()
133 133 self.check_log_dir()
134 134 self.check_pid_dir()
135 135 self.check_startup_dir()
136 136
137 137 def copy_config_file(self, config_file: str, path: Path, overwrite=False) -> bool:
138 138 """Copy a default config file into the active profile directory.
139 139
140 140 Default configuration files are kept in :mod:`IPython.core.profile`.
141 141 This function moves these from that location to the working profile
142 142 directory.
143 143 """
144 144 dst = Path(os.path.join(self.location, config_file))
145 145 if dst.exists() and not overwrite:
146 146 return False
147 147 if path is None:
148 148 path = os.path.join(get_ipython_package_dir(), u'core', u'profile', u'default')
149 149 assert isinstance(path, Path)
150 150 src = path / config_file
151 151 shutil.copy(src, dst)
152 152 return True
153 153
154 154 @classmethod
155 155 def create_profile_dir(cls, profile_dir, config=None):
156 156 """Create a new profile directory given a full path.
157 157
158 158 Parameters
159 159 ----------
160 160 profile_dir : str
161 161 The full path to the profile directory. If it does exist, it will
162 162 be used. If not, it will be created.
163 163 """
164 164 return cls(location=profile_dir, config=config)
165 165
166 166 @classmethod
167 167 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
168 168 """Create a profile dir by profile name and path.
169 169
170 170 Parameters
171 171 ----------
172 172 path : unicode
173 173 The path (directory) to put the profile directory in.
174 174 name : unicode
175 175 The name of the profile. The name of the profile directory will
176 176 be "profile_<profile>".
177 177 """
178 178 if not os.path.isdir(path):
179 179 raise ProfileDirError('Directory not found: %s' % path)
180 180 profile_dir = os.path.join(path, u'profile_' + name)
181 181 return cls(location=profile_dir, config=config)
182 182
183 183 @classmethod
184 184 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
185 185 """Find an existing profile dir by profile name, return its ProfileDir.
186 186
187 187 This searches through a sequence of paths for a profile dir. If it
188 188 is not found, a :class:`ProfileDirError` exception will be raised.
189 189
190 190 The search path algorithm is:
191 1. ``os.getcwd()``
191 1. ``os.getcwd()`` # removed for security reason.
192 192 2. ``ipython_dir``
193 193
194 194 Parameters
195 195 ----------
196 196 ipython_dir : unicode or str
197 197 The IPython directory to use.
198 198 name : unicode or str
199 199 The name of the profile. The name of the profile directory
200 200 will be "profile_<profile>".
201 201 """
202 202 dirname = u'profile_' + name
203 paths = [os.getcwd(), ipython_dir]
203 paths = [ipython_dir]
204 204 for p in paths:
205 205 profile_dir = os.path.join(p, dirname)
206 206 if os.path.isdir(profile_dir):
207 207 return cls(location=profile_dir, config=config)
208 208 else:
209 209 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
210 210
211 211 @classmethod
212 212 def find_profile_dir(cls, profile_dir, config=None):
213 213 """Find/create a profile dir and return its ProfileDir.
214 214
215 215 This will create the profile directory if it doesn't exist.
216 216
217 217 Parameters
218 218 ----------
219 219 profile_dir : unicode or str
220 220 The path of the profile directory.
221 221 """
222 222 profile_dir = expand_path(profile_dir)
223 223 if not os.path.isdir(profile_dir):
224 224 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
225 225 return cls(location=profile_dir, config=config)
@@ -1,906 +1,950 b''
1 1 ============
2 2 8.x Series
3 3 ============
4 4
5
6 IPython 8.0.1 (CVE-2022-21699)
7 ------------------------------
8
9 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
10 values in order to prevent potential Execution with Unnecessary Privileges.
11
12 Almost all version of IPython looks for configuration and profiles in current
13 working directory. Since IPython was developed before pip and environments
14 existed it was used a convenient way to load code/packages in a project
15 dependant way.
16
17 In 2022, it is not necessary anymore, and can lead to confusing behavior where
18 for example cloning a repository and starting IPython or loading a notebook from
19 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
20 code execution.
21
22
23 I did not find any standard way for packaged to advertise CVEs they fix, I'm
24 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
25 list the CVEs that should have been fixed. This attribute is informational only
26 as if a executable has a flaw, this value can always be changed by an attacker.
27
28 .. code::
29
30 In [1]: import IPython
31
32 In [2]: IPython.__patched_cves__
33 Out[2]: {'CVE-2022-21699'}
34
35 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
36 Out[3]: True
37
38 Thus starting with this version:
39
40 - The current working directory is not searched anymore for profiles or
41 configurations files.
42 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
43 the list of fixed CVE. This is informational only.
44
45 Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__
46
47
48
5 49 IPython 8.0
6 50 -----------
7 51
8 52 IPython 8.0 is still in alpha/beta stage. Please help us improve those release notes
9 53 by sending PRs that modify docs/source/whatsnew/version8.rst
10 54
11 55 IPython 8.0 is bringing a large number of new features and improvements to both the
12 56 user of the terminal and of the kernel via Jupyter. The removal of compatibility
13 57 with older version of Python is also the opportunity to do a couple of
14 58 performance improvement in particular with respect to startup time.
15 59 The 8.x branch started diverging from its predecessor around IPython 7.12
16 60 (January 2020).
17 61
18 62 This release contains 250+ Pull Requests, in addition to many of the features
19 63 and backports that have made it to the 7.x branch. All PRs that went into this
20 64 released are properly tagged with the 8.0 milestone if you wish to have a more
21 65 in depth look at the changes.
22 66
23 67 Please fell free to send pull-requests to updates those notes after release,
24 68 I have likely forgotten a few things reviewing 250+ PRs.
25 69
26 70 Dependencies changes/downstream packaging
27 71 -----------------------------------------
28 72
29 73 Note that most of our building step have been changes to be (mostly) declarative
30 74 and follow PEP 517, we are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
31 75 looking for help to do so.
32 76
33 77 - Minimum supported ``traitlets`` version if now 5+
34 78 - we now require ``stack_data``
35 79 - Minimal Python is now 3.8
36 80 - ``nose`` is not a testing requirement anymore
37 81 - ``pytest`` replaces nose.
38 82 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
39 83 - minimum officially support ``numpy`` version has been bumped, but this should
40 84 not have much effect on packaging.
41 85
42 86
43 87 Deprecation and removal
44 88 -----------------------
45 89
46 90 We removed almost all features, arguments, functions, and modules that were
47 91 marked as deprecated between IPython 1.0 and 5.0. As reminder 5.0 was released
48 92 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in may 2020.
49 93 The few remaining deprecated features we left have better deprecation warnings
50 94 or have been turned into explicit errors for better error messages.
51 95
52 96 I will use this occasion to add the following requests to anyone emitting a
53 97 deprecation warning:
54 98
55 99 - Please at at least ``stacklevel=2`` so that the warning is emitted into the
56 100 caller context, and not the callee one.
57 101 - Please add **since which version** something is deprecated.
58 102
59 103 As a side note it is much easier to deal with conditional comparing to versions
60 104 numbers than ``try/except`` when a functionality change with version.
61 105
62 106 I won't list all the removed features here, but modules like ``IPython.kernel``,
63 107 which was just a shim module around ``ipykernel`` for the past 8 years have been
64 108 remove, and so many other similar things that pre-date the name **Jupyter**
65 109 itself.
66 110
67 111 We no longer need to add ``IPyhton.extensions`` to the PYTHONPATH because that is being
68 112 handled by ``load_extension``.
69 113
70 114 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
71 115 other packages and no longer need to be inside IPython.
72 116
73 117
74 118 Documentation
75 119 -------------
76 120
77 121 Majority of our docstrings have now been reformatted and automatically fixed by
78 122 the experimental `VΓ©lin <https://pypi.org/project/velin/>`_ project, to conform
79 123 to numpydoc.
80 124
81 125 Type annotations
82 126 ----------------
83 127
84 128 While IPython itself is highly dynamic and can't be completely typed, many of
85 129 the function now have type annotation, and part of the codebase and now checked
86 130 by mypy.
87 131
88 132
89 133 Featured changes
90 134 ----------------
91 135
92 136 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
93 137 Please note as well that many features have been added in the 7.x branch as well
94 138 (and hence why you want to read the 7.x what's new notes), in particular
95 139 features contributed by QuantStack (with respect to debugger protocol, and Xeus
96 140 Python), as well as many debugger features that I was please to implement as
97 141 part of my work at QuanSight and Sponsored by DE Shaw.
98 142
99 143 Traceback improvements
100 144 ~~~~~~~~~~~~~~~~~~~~~~
101 145
102 146 Previously, error tracebacks for errors happening in code cells were showing a
103 147 hash, the one used for compiling the Python AST::
104 148
105 149 In [1]: def foo():
106 150 ...: return 3 / 0
107 151 ...:
108 152
109 153 In [2]: foo()
110 154 ---------------------------------------------------------------------------
111 155 ZeroDivisionError Traceback (most recent call last)
112 156 <ipython-input-2-c19b6d9633cf> in <module>
113 157 ----> 1 foo()
114 158
115 159 <ipython-input-1-1595a74c32d5> in foo()
116 160 1 def foo():
117 161 ----> 2 return 3 / 0
118 162 3
119 163
120 164 ZeroDivisionError: division by zero
121 165
122 166 The error traceback is now correctly formatted, showing the cell number in which the error happened::
123 167
124 168 In [1]: def foo():
125 169 ...: return 3 / 0
126 170 ...:
127 171
128 172 Input In [2]: foo()
129 173 ---------------------------------------------------------------------------
130 174 ZeroDivisionError Traceback (most recent call last)
131 175 input In [2], in <module>
132 176 ----> 1 foo()
133 177
134 178 Input In [1], in foo()
135 179 1 def foo():
136 180 ----> 2 return 3 / 0
137 181
138 182 ZeroDivisionError: division by zero
139 183
140 184 The Second on is the integration of the ``stack_data`` package;
141 185 which provide smarter informations in traceback; in particular it will highlight
142 186 the AST node where an error occurs which can help to quickly narrow down errors.
143 187
144 188 For example in the following snippet::
145 189
146 190 def foo(i):
147 191 x = [[[0]]]
148 192 return x[0][i][0]
149 193
150 194
151 195 def bar():
152 196 return foo(0) + foo(
153 197 1
154 198 ) + foo(2)
155 199
156 200
157 201 Calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
158 202 IPython 8.0 is capable of telling you, where the index error occurs::
159 203
160 204
161 205 IndexError
162 206 Input In [2], in <module>
163 207 ----> 1 bar()
164 208 ^^^^^
165 209
166 210 Input In [1], in bar()
167 211 6 def bar():
168 212 ----> 7 return foo(0) + foo(
169 213 ^^^^
170 214 8 1
171 215 ^^^^^^^^
172 216 9 ) + foo(2)
173 217 ^^^^
174 218
175 219 Input In [1], in foo(i)
176 220 1 def foo(i):
177 221 2 x = [[[0]]]
178 222 ----> 3 return x[0][i][0]
179 223 ^^^^^^^
180 224
181 225 Corresponding location marked here with ``^`` will show up highlighted in
182 226 terminal and notebooks.
183 227
184 228 The Third, which is the most discreet but can have a high impact on
185 229 productivity, a colon ``::`` and line number is appended after a filename in
186 230 traceback::
187 231
188 232
189 233 ZeroDivisionError Traceback (most recent call last)
190 234 File ~/error.py:4, in <module>
191 235 1 def f():
192 236 2 1/0
193 237 ----> 4 f()
194 238
195 239 File ~/error.py:2, in f()
196 240 1 def f():
197 241 ----> 2 1/0
198 242
199 243 Many terminal and editor have integrations allow to directly jump to the
200 244 relevant file/line when this syntax is used.
201 245
202 246
203 247 Autosuggestons
204 248 ~~~~~~~~~~~~~~
205 249
206 250 Autosuggestion is a very useful feature available in `fish <https://fishshell.com/>`__, `zsh <https://en.wikipedia.org/wiki/Z_shell>`__, and `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#auto-suggestion>`__.
207 251
208 252 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
209 253 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
210 254
211 255 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
212 256 or right arrow as described below.
213 257
214 258 1. Start ipython
215 259
216 260 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
217 261
218 262 2. Run ``print("hello")``
219 263
220 264 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
221 265
222 266 3. start typing ``print`` again to see the autosuggestion
223 267
224 268 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
225 269
226 270 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
227 271
228 272 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
229 273
230 274 You can also complete word by word:
231 275
232 276 1. Run ``def say_hello(): print("hello")``
233 277
234 278 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
235 279
236 280 2. Start typing the first letter if ``def`` to see the autosuggestion
237 281
238 282 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
239 283
240 284 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
241 285
242 286 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
243 287
244 288 Importantly, this feature does not interfere with tab completion:
245 289
246 290 1. After running ``def say_hello(): print("hello")``, press d
247 291
248 292 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
249 293
250 294 2. Press Tab to start tab completion
251 295
252 296 .. image:: ../_images/8.0/auto_suggest_d_completions.png
253 297
254 298 3A. Press Tab again to select the first option
255 299
256 300 .. image:: ../_images/8.0/auto_suggest_def_completions.png
257 301
258 302 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
259 303
260 304 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
261 305
262 306 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
263 307
264 308 .. image:: ../_images/8.0/auto_suggest_match_parens.png
265 309
266 310
267 311 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
268 312
269 313 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
270 314 - To use these shortcuts in vi insert mode, you will have to create `custom keybindings in your config.py <https://github.com/mskar/setup/commit/2892fcee46f9f80ef7788f0749edc99daccc52f4/>`__.
271 315
272 316
273 317 Show pinfo information in ipdb using "?" and "??"
274 318 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
275 319
276 320 In IPDB, it is now possible to show the information about an object using "?"
277 321 and "??", in much the same way it can be done when using the IPython prompt::
278 322
279 323 ipdb> partial?
280 324 Init signature: partial(self, /, *args, **kwargs)
281 325 Docstring:
282 326 partial(func, *args, **keywords) - new function with partial application
283 327 of the given arguments and keywords.
284 328 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
285 329 Type: type
286 330 Subclasses:
287 331
288 332 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
289 333
290 334
291 335 Autoreload 3 feature
292 336 ~~~~~~~~~~~~~~~~~~~~
293 337
294 338 Example: When an IPython session is ran with the 'autoreload' extension loaded,
295 339 you will now have the option '3' to select which means the following:
296 340
297 341 1. replicate all functionality from option 2
298 342 2. autoload all new funcs/classes/enums/globals from the module when they are added
299 343 3. autoload all newly imported funcs/classes/enums/globals from external modules
300 344
301 345 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``
302 346
303 347 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
304 348
305 349 Auto formatting with black in the CLI
306 350 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
307 351
308 352 If ``black`` is installed in the same environment as IPython, terminal IPython
309 353 will now *by default* reformat the code in the CLI when possible. You can
310 354 disable this with ``--TerminalInteractiveShell.autoformatter=None``.
311 355
312 356 This feature was present in 7.x but disabled by default.
313 357
314 358
315 359 History Range Glob feature
316 360 ~~~~~~~~~~~~~~~~~~~~~~~~~~
317 361
318 362 Previously, when using ``%history``, users could specify either
319 363 a range of sessions and lines, for example:
320 364
321 365 .. code-block:: python
322 366
323 367 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
324 368 # to the fifth line of 6 sessions ago.``
325 369
326 370 Or users could specify a glob pattern:
327 371
328 372 .. code-block:: python
329 373
330 374 -g <pattern> # glob ALL history for the specified pattern.
331 375
332 376 However users could *not* specify both.
333 377
334 378 If a user *did* specify both a range and a glob pattern,
335 379 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
336 380
337 381 With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history.
338 382
339 383 Don't start a multi line cell with sunken parenthesis
340 384 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
341 385
342 386 From now on IPython will not ask for the next line of input when given a single
343 387 line with more closing than opening brackets. For example, this means that if
344 388 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
345 389 the ``...:`` prompt continuation.
346 390
347 391 IPython shell for ipdb interact
348 392 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
349 393
350 394 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
351 395
352 396 Automatic Vi prompt stripping
353 397 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
354 398
355 399 When pasting code into IPython, it will strip the leading prompt characters if
356 400 there are any. For example, you can paste the following code into the console -
357 401 it will still work, even though each line is prefixed with prompts (`In`,
358 402 `Out`)::
359 403
360 404 In [1]: 2 * 2 == 4
361 405 Out[1]: True
362 406
363 407 In [2]: print("This still works as pasted")
364 408
365 409
366 410 Previously, this was not the case for the Vi-mode prompts::
367 411
368 412 In [1]: [ins] In [13]: 2 * 2 == 4
369 413 ...: Out[13]: True
370 414 ...:
371 415 File "<ipython-input-1-727bb88eaf33>", line 1
372 416 [ins] In [13]: 2 * 2 == 4
373 417 ^
374 418 SyntaxError: invalid syntax
375 419
376 420 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
377 421 skipped just as the normal ``In`` would be.
378 422
379 423 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
380 424 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
381 425
382 426 Empty History Ranges
383 427 ~~~~~~~~~~~~~~~~~~~~
384 428
385 429 A number of magics that take history ranges can now be used with an empty
386 430 range. These magics are:
387 431
388 432 * ``%save``
389 433 * ``%load``
390 434 * ``%pastebin``
391 435 * ``%pycat``
392 436
393 437 Using them this way will make them take the history of the current session up
394 438 to the point of the magic call (such that the magic itself will not be
395 439 included).
396 440
397 441 Therefore it is now possible to save the whole history to a file using simple
398 442 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
399 443 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
400 444 ``%pastebin``, or view the whole thing syntax-highlighted with a single
401 445 ``%pycat``.
402 446
403 447
404 448 Windows time-implementation: Switch to process_time
405 449 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
406 450 Timing for example with ``%%time`` on windows is based on ``time.perf_counter``.
407 451 This is at the end the same as W-All.
408 452 To be a bit tighter to linux one could change to ``time.process_time`` instead.
409 453 Thus for example one would no longer count periods of sleep and further.
410 454
411 455
412 456 Miscellaneous
413 457 ~~~~~~~~~~~~~
414 458 - Non-text formatters are not disabled in terminal which should simplify
415 459 writing extension displaying images or other mimetypes supporting terminals.
416 460 :ghpull:`12315`
417 461 -
418 462 - It is now possible to automatically insert matching brackets in Terminal IPython using the
419 463 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
420 464 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`
421 465 - ``%time`` uses ``process_time`` instead of ``perf_counter``, see :ghpull:`12984`
422 466 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
423 467 - ``%/%%timeit`` magic now adds comma every thousands to make reading long number easier :ghpull:`13379`
424 468 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
425 469 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
426 470 - The debugger now have a persistent history, which should make it less
427 471 annoying to retype commands :ghpull:`13246`
428 472 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing, we
429 473 now warn users if they use it. :ghpull:`12954`
430 474 - make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
431 475
432 476 Re-added support for XDG config directories
433 477 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434 478
435 479 XDG support through the years did come an go, there is a tension between having
436 480 identical location in all platforms to have simple instructions. After initial
437 481 failure a couple of years ago IPython was modified to automatically migrate XDG
438 482 config files back into ``~/.ipython``, the migration code has now been removed.
439 483 And IPython now check the XDG locations, so if you _manually_ move your config
440 484 files to your preferred location, IPython will not move them back.
441 485
442 486
443 487 Numfocus Small Developer Grant
444 488 ------------------------------
445 489
446 490 To prepare for Python 3.10 we have also started working on removing reliance and
447 491 any dependency that is not Python 3.10 compatible; that include migrating our
448 492 test suite to pytest, and starting to remove nose. This also mean that the
449 493 ``iptest`` command is now gone, and all testing is via pytest.
450 494
451 495 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
452 496 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
453 497 who did a fantastic job at updating our code base, migrating to pytest, pushing
454 498 our coverage, and fixing a large number of bugs. I highly recommend contacting
455 499 them if you need help with C++ and Python projects
456 500
457 501 You can find all relevant issues and PRs with the SDG 2021 tag `<https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+>`__
458 502
459 503 Removing support for Older Python
460 504 ---------------------------------
461 505
462 506
463 507 We are also removing support for Python up to 3.7 allowing internal code to use more
464 508 efficient ``pathlib``, and make better use of type annotations.
465 509
466 510 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
467 511 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
468 512
469 513
470 514 We have about 34 PRs only to update some logic to update some functions from managing strings to
471 515 using Pathlib.
472 516
473 517 The completer has also seen significant updates and make use of newer Jedi API
474 518 offering faster and more reliable tab completion.
475 519
476 520 Misc Statistics
477 521 ---------------
478 522
479 523 Here are some numbers:
480 524
481 525 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
482 526 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
483 527
484 528 $ git diff --stat 7.x...master | tail -1
485 529 340 files changed, 13399 insertions(+), 12421 deletions(-)
486 530
487 531 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges to not bias toward
488 532 maintainers pushing buttons.::
489 533
490 534 $ git shortlog -s --no-merges 7.x...master | sort -nr
491 535 535 Matthias Bussonnier
492 536 86 Nikita Kniazev
493 537 69 Blazej Michalik
494 538 49 Samuel Gaist
495 539 27 Itamar Turner-Trauring
496 540 18 Spas Kalaydzhisyki
497 541 17 Thomas Kluyver
498 542 17 Quentin Peter
499 543 17 James Morris
500 544 17 Artur Svistunov
501 545 15 Bart Skowron
502 546 14 Alex Hall
503 547 13 rushabh-v
504 548 13 Terry Davis
505 549 13 Benjamin Ragan-Kelley
506 550 8 martinRenou
507 551 8 farisachugthai
508 552 7 dswij
509 553 7 Gal B
510 554 7 Corentin Cadiou
511 555 6 yuji96
512 556 6 Martin Skarzynski
513 557 6 Justin Palmer
514 558 6 Daniel Goldfarb
515 559 6 Ben Greiner
516 560 5 Sammy Al Hashemi
517 561 5 Paul Ivanov
518 562 5 Inception95
519 563 5 Eyenpi
520 564 5 Douglas Blank
521 565 5 Coco Mishra
522 566 5 Bibo Hao
523 567 5 AndrΓ© A. Gomes
524 568 5 Ahmed Fasih
525 569 4 takuya fujiwara
526 570 4 palewire
527 571 4 Thomas A Caswell
528 572 4 Talley Lambert
529 573 4 Scott Sanderson
530 574 4 Ram Rachum
531 575 4 Nick Muoh
532 576 4 Nathan Goldbaum
533 577 4 Mithil Poojary
534 578 4 Michael T
535 579 4 Jakub Klus
536 580 4 Ian Castleden
537 581 4 Eli Rykoff
538 582 4 Ashwin Vishnu
539 583 3 谭九鼎
540 584 3 sleeping
541 585 3 Sylvain Corlay
542 586 3 Peter Corke
543 587 3 Paul Bissex
544 588 3 Matthew Feickert
545 589 3 Fernando Perez
546 590 3 Eric Wieser
547 591 3 Daniel Mietchen
548 592 3 Aditya Sathe
549 593 3 007vedant
550 594 2 rchiodo
551 595 2 nicolaslazo
552 596 2 luttik
553 597 2 gorogoroumaru
554 598 2 foobarbyte
555 599 2 bar-hen
556 600 2 Theo Ouzhinski
557 601 2 Strawkage
558 602 2 Samreen Zarroug
559 603 2 Pete Blois
560 604 2 Meysam Azad
561 605 2 Matthieu Ancellin
562 606 2 Mark Schmitz
563 607 2 Maor Kleinberger
564 608 2 MRCWirtz
565 609 2 Lumir Balhar
566 610 2 Julien Rabinow
567 611 2 Juan Luis Cano RodrΓ­guez
568 612 2 Joyce Er
569 613 2 Jakub
570 614 2 Faris A Chugthai
571 615 2 Ethan Madden
572 616 2 Dimitri Papadopoulos
573 617 2 Diego Fernandez
574 618 2 Daniel Shimon
575 619 2 Coco Bennett
576 620 2 Carlos Cordoba
577 621 2 Boyuan Liu
578 622 2 BaoGiang HoangVu
579 623 2 Augusto
580 624 2 Arthur Svistunov
581 625 2 Arthur Moreira
582 626 2 Ali Nabipour
583 627 2 Adam Hackbarth
584 628 1 richard
585 629 1 linar-jether
586 630 1 lbennett
587 631 1 juacrumar
588 632 1 gpotter2
589 633 1 digitalvirtuoso
590 634 1 dalthviz
591 635 1 Yonatan Goldschmidt
592 636 1 Tomasz KΕ‚oczko
593 637 1 Tobias Bengfort
594 638 1 Timur Kushukov
595 639 1 Thomas
596 640 1 Snir Broshi
597 641 1 Shao Yang Hong
598 642 1 Sanjana-03
599 643 1 Romulo Filho
600 644 1 Rodolfo Carvalho
601 645 1 Richard Shadrach
602 646 1 Reilly Tucker Siemens
603 647 1 Rakessh Roshan
604 648 1 Piers Titus van der Torren
605 649 1 PhanatosZou
606 650 1 Pavel Safronov
607 651 1 Paulo S. Costa
608 652 1 Paul McCarthy
609 653 1 NotWearingPants
610 654 1 Naelson Douglas
611 655 1 Michael Tiemann
612 656 1 Matt Wozniski
613 657 1 Markus Wageringel
614 658 1 Marcus Wirtz
615 659 1 Marcio Mazza
616 660 1 LumΓ­r 'Frenzy' Balhar
617 661 1 Lightyagami1
618 662 1 Leon Anavi
619 663 1 LeafyLi
620 664 1 L0uisJ0shua
621 665 1 Kyle Cutler
622 666 1 Krzysztof Cybulski
623 667 1 Kevin Kirsche
624 668 1 KIU Shueng Chuan
625 669 1 Jonathan Slenders
626 670 1 Jay Qi
627 671 1 Jake VanderPlas
628 672 1 Iwan Briquemont
629 673 1 Hussaina Begum Nandyala
630 674 1 Gordon Ball
631 675 1 Gabriel Simonetto
632 676 1 Frank Tobia
633 677 1 Erik
634 678 1 Elliott Sales de Andrade
635 679 1 Daniel Hahler
636 680 1 Dan Green-Leipciger
637 681 1 Dan Green
638 682 1 Damian Yurzola
639 683 1 Coon, Ethan T
640 684 1 Carol Willing
641 685 1 Brian Lee
642 686 1 Brendan Gerrity
643 687 1 Blake Griffin
644 688 1 Bastian Ebeling
645 689 1 Bartosz Telenczuk
646 690 1 Ankitsingh6299
647 691 1 Andrew Port
648 692 1 Andrew J. Hesford
649 693 1 Albert Zhang
650 694 1 Adam Johnson
651 695
652 696 This does not of course represent non-code contributions.
653 697
654 698
655 699 API Changes using Frappuccino
656 700 -----------------------------
657 701
658 702 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
659 703
660 704
661 705 The following items are new in IPython 8.0 ::
662 706
663 707 + IPython.core.async_helpers.get_asyncio_loop()
664 708 + IPython.core.completer.Dict
665 709 + IPython.core.completer.Pattern
666 710 + IPython.core.completer.Sequence
667 711 + IPython.core.completer.__skip_doctest__
668 712 + IPython.core.debugger.Pdb.precmd(self, line)
669 713 + IPython.core.debugger.__skip_doctest__
670 714 + IPython.core.display.__getattr__(name)
671 715 + IPython.core.display.warn
672 716 + IPython.core.display_functions
673 717 + IPython.core.display_functions.DisplayHandle
674 718 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
675 719 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
676 720 + IPython.core.display_functions.__all__
677 721 + IPython.core.display_functions.__builtins__
678 722 + IPython.core.display_functions.__cached__
679 723 + IPython.core.display_functions.__doc__
680 724 + IPython.core.display_functions.__file__
681 725 + IPython.core.display_functions.__loader__
682 726 + IPython.core.display_functions.__name__
683 727 + IPython.core.display_functions.__package__
684 728 + IPython.core.display_functions.__spec__
685 729 + IPython.core.display_functions.b2a_hex
686 730 + IPython.core.display_functions.clear_output(wait=False)
687 731 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
688 732 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
689 733 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
690 734 + IPython.core.extensions.BUILTINS_EXTS
691 735 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
692 736 + IPython.core.interactiveshell.Callable
693 737 + IPython.core.interactiveshell.__annotations__
694 738 + IPython.core.ultratb.List
695 739 + IPython.core.ultratb.Tuple
696 740 + IPython.lib.pretty.CallExpression
697 741 + IPython.lib.pretty.CallExpression.factory(name)
698 742 + IPython.lib.pretty.RawStringLiteral
699 743 + IPython.lib.pretty.RawText
700 744 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
701 745 + IPython.terminal.embed.Set
702 746
703 747 The following items have been removed (or moved to superclass)::
704 748
705 749 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
706 750 - IPython.core.completer.Sentinel
707 751 - IPython.core.completer.skip_doctest
708 752 - IPython.core.debugger.Tracer
709 753 - IPython.core.display.DisplayHandle
710 754 - IPython.core.display.DisplayHandle.display
711 755 - IPython.core.display.DisplayHandle.update
712 756 - IPython.core.display.b2a_hex
713 757 - IPython.core.display.clear_output
714 758 - IPython.core.display.display
715 759 - IPython.core.display.publish_display_data
716 760 - IPython.core.display.update_display
717 761 - IPython.core.excolors.Deprec
718 762 - IPython.core.excolors.ExceptionColors
719 763 - IPython.core.history.warn
720 764 - IPython.core.hooks.late_startup_hook
721 765 - IPython.core.hooks.pre_run_code_hook
722 766 - IPython.core.hooks.shutdown_hook
723 767 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
724 768 - IPython.core.interactiveshell.InteractiveShell.init_readline
725 769 - IPython.core.interactiveshell.InteractiveShell.write
726 770 - IPython.core.interactiveshell.InteractiveShell.write_err
727 771 - IPython.core.interactiveshell.get_default_colors
728 772 - IPython.core.interactiveshell.removed_co_newlocals
729 773 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
730 774 - IPython.core.magics.script.PIPE
731 775 - IPython.core.prefilter.PrefilterManager.init_transformers
732 776 - IPython.core.release.classifiers
733 777 - IPython.core.release.description
734 778 - IPython.core.release.keywords
735 779 - IPython.core.release.long_description
736 780 - IPython.core.release.name
737 781 - IPython.core.release.platforms
738 782 - IPython.core.release.url
739 783 - IPython.core.ultratb.VerboseTB.format_records
740 784 - IPython.core.ultratb.find_recursion
741 785 - IPython.core.ultratb.findsource
742 786 - IPython.core.ultratb.fix_frame_records_filenames
743 787 - IPython.core.ultratb.inspect_error
744 788 - IPython.core.ultratb.is_recursion_error
745 789 - IPython.core.ultratb.with_patch_inspect
746 790 - IPython.external.__all__
747 791 - IPython.external.__builtins__
748 792 - IPython.external.__cached__
749 793 - IPython.external.__doc__
750 794 - IPython.external.__file__
751 795 - IPython.external.__loader__
752 796 - IPython.external.__name__
753 797 - IPython.external.__package__
754 798 - IPython.external.__path__
755 799 - IPython.external.__spec__
756 800 - IPython.kernel.KernelConnectionInfo
757 801 - IPython.kernel.__builtins__
758 802 - IPython.kernel.__cached__
759 803 - IPython.kernel.__warningregistry__
760 804 - IPython.kernel.pkg
761 805 - IPython.kernel.protocol_version
762 806 - IPython.kernel.protocol_version_info
763 807 - IPython.kernel.src
764 808 - IPython.kernel.version_info
765 809 - IPython.kernel.warn
766 810 - IPython.lib.backgroundjobs
767 811 - IPython.lib.backgroundjobs.BackgroundJobBase
768 812 - IPython.lib.backgroundjobs.BackgroundJobBase.run
769 813 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
770 814 - IPython.lib.backgroundjobs.BackgroundJobExpr
771 815 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
772 816 - IPython.lib.backgroundjobs.BackgroundJobFunc
773 817 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
774 818 - IPython.lib.backgroundjobs.BackgroundJobManager
775 819 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
776 820 - IPython.lib.backgroundjobs.BackgroundJobManager.new
777 821 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
778 822 - IPython.lib.backgroundjobs.BackgroundJobManager.result
779 823 - IPython.lib.backgroundjobs.BackgroundJobManager.status
780 824 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
781 825 - IPython.lib.backgroundjobs.__builtins__
782 826 - IPython.lib.backgroundjobs.__cached__
783 827 - IPython.lib.backgroundjobs.__doc__
784 828 - IPython.lib.backgroundjobs.__file__
785 829 - IPython.lib.backgroundjobs.__loader__
786 830 - IPython.lib.backgroundjobs.__name__
787 831 - IPython.lib.backgroundjobs.__package__
788 832 - IPython.lib.backgroundjobs.__spec__
789 833 - IPython.lib.kernel.__builtins__
790 834 - IPython.lib.kernel.__cached__
791 835 - IPython.lib.kernel.__doc__
792 836 - IPython.lib.kernel.__file__
793 837 - IPython.lib.kernel.__loader__
794 838 - IPython.lib.kernel.__name__
795 839 - IPython.lib.kernel.__package__
796 840 - IPython.lib.kernel.__spec__
797 841 - IPython.lib.kernel.__warningregistry__
798 842 - IPython.paths.fs_encoding
799 843 - IPython.terminal.debugger.DEFAULT_BUFFER
800 844 - IPython.terminal.debugger.cursor_in_leading_ws
801 845 - IPython.terminal.debugger.emacs_insert_mode
802 846 - IPython.terminal.debugger.has_selection
803 847 - IPython.terminal.debugger.vi_insert_mode
804 848 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
805 849 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
806 850 - IPython.testing.test
807 851 - IPython.utils.contexts.NoOpContext
808 852 - IPython.utils.io.IOStream
809 853 - IPython.utils.io.IOStream.close
810 854 - IPython.utils.io.IOStream.write
811 855 - IPython.utils.io.IOStream.writelines
812 856 - IPython.utils.io.__warningregistry__
813 857 - IPython.utils.io.atomic_writing
814 858 - IPython.utils.io.stderr
815 859 - IPython.utils.io.stdin
816 860 - IPython.utils.io.stdout
817 861 - IPython.utils.io.unicode_std_stream
818 862 - IPython.utils.path.get_ipython_cache_dir
819 863 - IPython.utils.path.get_ipython_dir
820 864 - IPython.utils.path.get_ipython_module_path
821 865 - IPython.utils.path.get_ipython_package_dir
822 866 - IPython.utils.path.locate_profile
823 867 - IPython.utils.path.unquote_filename
824 868 - IPython.utils.py3compat.PY2
825 869 - IPython.utils.py3compat.PY3
826 870 - IPython.utils.py3compat.buffer_to_bytes
827 871 - IPython.utils.py3compat.builtin_mod_name
828 872 - IPython.utils.py3compat.cast_bytes
829 873 - IPython.utils.py3compat.getcwd
830 874 - IPython.utils.py3compat.isidentifier
831 875 - IPython.utils.py3compat.u_format
832 876
833 877 The following signatures differ between 7.x and 8.0::
834 878
835 879 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
836 880 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
837 881
838 882 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
839 883 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
840 884
841 885 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
842 886 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
843 887
844 888 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
845 889 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
846 890
847 891 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
848 892 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
849 893
850 894 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
851 895 + IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True, omit_sections='()')
852 896
853 897 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
854 898 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
855 899
856 900 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
857 901 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
858 902
859 903 - IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, display_banner='None', global_ns='None', compile_flags='None')
860 904 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
861 905
862 906 - IPython.terminal.embed.embed(**kwargs)
863 907 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
864 908
865 909 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
866 910 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
867 911
868 912 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
869 913 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
870 914
871 915 - IPython.utils.path.get_py_filename(name, force_win32='None')
872 916 + IPython.utils.path.get_py_filename(name)
873 917
874 918 The following are new attributes (that might be inherited)::
875 919
876 920 + IPython.core.completer.IPCompleter.unicode_names
877 921 + IPython.core.debugger.InterruptiblePdb.precmd
878 922 + IPython.core.debugger.Pdb.precmd
879 923 + IPython.core.ultratb.AutoFormattedTB.has_colors
880 924 + IPython.core.ultratb.ColorTB.has_colors
881 925 + IPython.core.ultratb.FormattedTB.has_colors
882 926 + IPython.core.ultratb.ListTB.has_colors
883 927 + IPython.core.ultratb.SyntaxTB.has_colors
884 928 + IPython.core.ultratb.TBTools.has_colors
885 929 + IPython.core.ultratb.VerboseTB.has_colors
886 930 + IPython.terminal.debugger.TerminalPdb.do_interact
887 931 + IPython.terminal.debugger.TerminalPdb.precmd
888 932
889 933 The following attribute/methods have been removed::
890 934
891 935 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
892 936 - IPython.core.ultratb.AutoFormattedTB.format_records
893 937 - IPython.core.ultratb.ColorTB.format_records
894 938 - IPython.core.ultratb.FormattedTB.format_records
895 939 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
896 940 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
897 941 - IPython.terminal.embed.InteractiveShellEmbed.write
898 942 - IPython.terminal.embed.InteractiveShellEmbed.write_err
899 943 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
900 944 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
901 945 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
902 946 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
903 947 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
904 948 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
905 949 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
906 950 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
General Comments 0
You need to be logged in to leave comments. Login now