diff --git a/newsfragments/2702.removal.rst b/newsfragments/2702.removal.rst new file mode 100644 index 0000000000..900da04498 --- /dev/null +++ b/newsfragments/2702.removal.rst @@ -0,0 +1 @@ +Removed special ``MultiError`` traceback handling for IPython. As of `version 8.15 `_ `ExceptionGroup` is handled natively. diff --git a/test-requirements.in b/test-requirements.in index 1911b1bf11..7461a1e4ed 100644 --- a/test-requirements.in +++ b/test-requirements.in @@ -3,7 +3,6 @@ pytest >= 5.0 # for faulthandler in core coverage >= 7.2.5 async_generator >= 1.9 pyright -ipython # for the IPython traceback integration tests pyOpenSSL >= 22.0.0 # for the ssl + DTLS tests trustme # for the ssl + DTLS tests pylint # for pylint finding all symbols tests diff --git a/test-requirements.txt b/test-requirements.txt index 86a8f14aee..2c86c7439c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,16 +8,12 @@ astor==0.8.1 # via -r test-requirements.in astroid==2.15.6 # via pylint -asttokens==2.2.1 - # via stack-data async-generator==1.10 # via -r test-requirements.in attrs==23.1.0 # via # -r test-requirements.in # outcome -backcall==0.2.0 - # via ipython black==23.7.0 ; implementation_name == "cpython" # via -r test-requirements.in build==0.10.0 @@ -38,16 +34,12 @@ cryptography==41.0.3 # pyopenssl # trustme # types-pyopenssl -decorator==5.1.1 - # via ipython dill==0.3.7 # via pylint exceptiongroup==1.1.3 ; python_version < "3.11" # via # -r test-requirements.in # pytest -executing==1.2.0 - # via stack-data flake8==6.1.0 # via # -r test-requirements.in @@ -60,18 +52,12 @@ idna==3.4 # trustme iniconfig==2.0.0 # via pytest -ipython==8.12.2 - # via -r test-requirements.in isort==5.12.0 # via pylint jedi==0.19.0 - # via - # -r test-requirements.in - # ipython + # via -r test-requirements.in lazy-object-proxy==1.9.0 # via astroid -matplotlib-inline==0.1.6 - # via ipython mccabe==0.7.0 # via # flake8 @@ -96,10 +82,6 @@ parso==0.8.3 # via jedi pathspec==0.11.2 # via black -pexpect==4.8.0 - # via ipython -pickleshare==0.7.5 - # via ipython pip-tools==7.3.0 # via -r test-requirements.in platformdirs==3.10.0 @@ -108,20 +90,12 @@ platformdirs==3.10.0 # pylint pluggy==1.3.0 # via pytest -prompt-toolkit==3.0.39 - # via ipython -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.2 - # via stack-data pycodestyle==2.11.0 # via flake8 pycparser==2.21 # via cffi pyflakes==3.1.0 # via flake8 -pygments==2.16.1 - # via ipython pylint==2.17.5 # via -r test-requirements.in pyopenssl==23.2.0 @@ -132,14 +106,10 @@ pyright==1.1.325 # via -r test-requirements.in pytest==7.4.0 # via -r test-requirements.in -six==1.16.0 - # via asttokens sniffio==1.3.0 # via -r test-requirements.in sortedcontainers==2.4.0 # via -r test-requirements.in -stack-data==0.6.2 - # via ipython tomli==2.0.1 # via # black @@ -152,10 +122,6 @@ tomli==2.0.1 # pytest tomlkit==0.12.1 # via pylint -traitlets==5.9.0 - # via - # ipython - # matplotlib-inline trustme==1.1.0 # via -r test-requirements.in types-pyopenssl==23.2.0.2 ; implementation_name == "cpython" @@ -165,11 +131,8 @@ typing-extensions==4.7.1 # -r test-requirements.in # astroid # black - # ipython # mypy # pylint -wcwidth==0.2.6 - # via prompt-toolkit wheel==0.41.2 # via pip-tools wrapt==1.15.0 diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index d55e89554d..45d725f2c6 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -1,7 +1,6 @@ from __future__ import annotations import sys -import warnings from collections.abc import Callable, Sequence from types import TracebackType from typing import TYPE_CHECKING, Any, cast, overload @@ -11,9 +10,7 @@ from trio._deprecate import warn_deprecated if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup, ExceptionGroup, print_exception -else: - from traceback import print_exception + from exceptiongroup import BaseExceptionGroup, ExceptionGroup if TYPE_CHECKING: from typing_extensions import Self @@ -461,37 +458,6 @@ def concat_tb( return current_head -# Remove when IPython gains support for exception groups -# (https://github.com/ipython/ipython/issues/13753) -if "IPython" in sys.modules: - import IPython - - ip = IPython.get_ipython() - if ip is not None: - if ip.custom_exceptions != (): - warnings.warn( - "IPython detected, but you already have a custom exception " - "handler installed. I'll skip installing Trio's custom " - "handler, but this means exception groups will not show full " - "tracebacks.", - category=RuntimeWarning, - ) - else: - - def trio_show_traceback( - self: IPython.core.interactiveshell.InteractiveShell, - etype: type[BaseException], - value: BaseException, - tb: TracebackType, - tb_offset: int | None = None, - ) -> None: - # XX it would be better to integrate with IPython's fancy - # exception formatting stuff (and not ignore tb_offset) - print_exception(value) - - ip.set_custom_exc((BaseExceptionGroup,), trio_show_traceback) - - # Ubuntu's system Python has a sitecustomize.py file that import # apport_python_hook and replaces sys.excepthook. # diff --git a/trio/_core/_tests/test_multierror.py b/trio/_core/_tests/test_multierror.py index 6990a7b756..6d9fd2a568 100644 --- a/trio/_core/_tests/test_multierror.py +++ b/trio/_core/_tests/test_multierror.py @@ -430,7 +430,7 @@ def test_non_base_multierror(): assert isinstance(exc, ExceptionGroup) -def run_script(name, use_ipython=False): +def run_script(name: str) -> subprocess.CompletedProcess[bytes]: import trio trio_path = Path(trio.__file__).parent.parent @@ -447,24 +447,7 @@ def run_script(name, use_ipython=False): env["PYTHONPATH"] = os.pathsep.join(pp) print("subprocess PYTHONPATH:", env.get("PYTHONPATH")) - if use_ipython: - lines = [ - "import runpy", - f"runpy.run_path(r'{script_path}', run_name='trio.fake')", - "exit()", - ] - - cmd = [ - sys.executable, - "-u", - "-m", - "IPython", - # no startup files - "--quick", - "--TerminalIPythonApp.code_to_run=" + "\n".join(lines), - ] - else: - cmd = [sys.executable, "-u", str(script_path)] + cmd = [sys.executable, "-u", str(script_path)] print("running:", cmd) completed = subprocess.run( cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT @@ -474,69 +457,6 @@ def run_script(name, use_ipython=False): return completed -def check_simple_excepthook(completed): - assert_match_in_seq( - [ - "in ", - "MultiError", - "--- 1 ---", - "in exc1_fn", - "ValueError", - "--- 2 ---", - "in exc2_fn", - "KeyError", - ], - completed.stdout.decode("utf-8"), - ) - - -try: - import IPython # noqa: F401 -except ImportError: # pragma: no cover - have_ipython = False -else: - have_ipython = True - -need_ipython = pytest.mark.skipif(not have_ipython, reason="need IPython") - - -@slow -@need_ipython -def test_ipython_exc_handler(): - completed = run_script("simple_excepthook.py", use_ipython=True) - check_simple_excepthook(completed) - - -@slow -@need_ipython -def test_ipython_imported_but_unused(): - completed = run_script("simple_excepthook_IPython.py") - check_simple_excepthook(completed) - - -@slow -@need_ipython -def test_ipython_custom_exc_handler(): - # Check we get a nice warning (but only one!) if the user is using IPython - # and already has some other set_custom_exc handler installed. - completed = run_script("ipython_custom_exc.py", use_ipython=True) - assert_match_in_seq( - [ - # The warning - "RuntimeWarning", - "IPython detected", - "skip installing Trio", - # The MultiError - "MultiError", - "ValueError", - "KeyError", - ], - completed.stdout.decode("utf-8"), - ) - # Make sure our other warning doesn't show up - assert "custom sys.excepthook" not in completed.stdout.decode("utf-8") - - @slow @pytest.mark.skipif( not Path("/usr/lib/python3/dist-packages/apport_python_hook.py").exists(), diff --git a/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py b/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py deleted file mode 100644 index 7ccb341dc9..0000000000 --- a/trio/_core/_tests/test_multierror_scripts/ipython_custom_exc.py +++ /dev/null @@ -1,36 +0,0 @@ -# Override the regular excepthook too -- it doesn't change anything either way -# because ipython doesn't use it, but we want to make sure Trio doesn't warn -# about it. -import sys - -import _common # isort: split - - -def custom_excepthook(*args): - print("custom running!") - return sys.__excepthook__(*args) - - -sys.excepthook = custom_excepthook - -import IPython - -ip = IPython.get_ipython() - - -# Set this to some random nonsense -class SomeError(Exception): - pass - - -def custom_exc_hook(etype, value, tb, tb_offset=None): - ip.showtraceback() - - -ip.set_custom_exc((SomeError,), custom_exc_hook) - -from trio._core._multierror import MultiError # Bypass deprecation warnings. - -# The custom excepthook should run, because Trio was polite and didn't -# override it -raise MultiError([ValueError(), KeyError()]) diff --git a/trio/_core/_tests/test_multierror_scripts/simple_excepthook_IPython.py b/trio/_core/_tests/test_multierror_scripts/simple_excepthook_IPython.py deleted file mode 100644 index 51a88c96ce..0000000000 --- a/trio/_core/_tests/test_multierror_scripts/simple_excepthook_IPython.py +++ /dev/null @@ -1,6 +0,0 @@ -import _common - -# To tickle the "is IPython loaded?" logic, make sure that Trio tolerates -# IPython loaded but not actually in use -import IPython -import simple_excepthook