From dec0c0b2464e579d9f05a957cb7a5571e8bcb179 Mon Sep 17 00:00:00 2001 From: Nathan Page Date: Sun, 7 Feb 2021 13:46:46 -0800 Subject: [PATCH 1/5] add the ability for traceback to install in ipython --- rich/traceback.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/rich/traceback.py b/rich/traceback.py index 488709dc..757737a0 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -72,7 +72,7 @@ def install( def excepthook( type_: Type[BaseException], value: BaseException, - traceback: TracebackType, + traceback: Optional[TracebackType], ) -> None: traceback_console.print( Traceback.from_exception( @@ -88,9 +88,30 @@ def install( ) ) - old_excepthook = sys.excepthook - sys.excepthook = excepthook - return old_excepthook + try: # pragma: no cover + ip = get_ipython() # type: ignore + + def ipy_excepthook(is_syntax: bool = False) -> None: + exc_tuple = ip._get_exc_info() + + # syntax errors will show no traceback + if is_syntax: + exc_tuple = exc_tuple[0], exc_tuple[1], None + else: + # remove traceback of ipython exec call + exc_tuple = exc_tuple[0], exc_tuple[1], exc_tuple[2].tb_next + excepthook(*exc_tuple) # type: ignore + + # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work + # this is also what the ipython docs recommends to modify when subcalssing InteractiveShell + ip._showtraceback = lambda *args, **kwargs: ipy_excepthook() + ip.showsyntaxerror = lambda *args, **kwargs: ipy_excepthook(is_syntax=True) + + return sys.excepthook + except Exception: + old_excepthook = sys.excepthook + sys.excepthook = excepthook + return old_excepthook @dataclass From 0d731c5d5ade753da365faaddc0245d1f1284899 Mon Sep 17 00:00:00 2001 From: Nathan Page Date: Sun, 7 Feb 2021 13:51:15 -0800 Subject: [PATCH 2/5] format with black --- rich/live_render.py | 2 +- tests/test_console.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rich/live_render.py b/rich/live_render.py index 9b62bd01..475619d7 100644 --- a/rich/live_render.py +++ b/rich/live_render.py @@ -88,4 +88,4 @@ class LiveRender: for last, line in loop_last(lines): yield from line if not last: - yield _Segment.line() \ No newline at end of file + yield _Segment.line() diff --git a/tests/test_console.py b/tests/test_console.py index c7ec3e7a..60b80a27 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -491,4 +491,4 @@ def test_no_nested_live(): with pytest.raises(errors.LiveError): with console.status("foo"): with console.status("bar"): - pass \ No newline at end of file + pass From 22d55908d261ef103778d9af41a947c4e44ac3b6 Mon Sep 17 00:00:00 2001 From: Nathan Page Date: Sun, 7 Feb 2021 13:53:25 -0800 Subject: [PATCH 3/5] fix mypy typing extension for Literal --- rich/live_render.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rich/live_render.py b/rich/live_render.py index 475619d7..0091f4d8 100644 --- a/rich/live_render.py +++ b/rich/live_render.py @@ -1,11 +1,13 @@ -from typing import Literal, Optional, Tuple +from typing import Optional, Tuple +from typing_extensions import Literal + +from ._loop import loop_last from .console import Console, ConsoleOptions, RenderableType, RenderResult from .control import Control from .segment import Segment -from .text import Text from .style import StyleType -from ._loop import loop_last +from .text import Text VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] From 1d08a76c2e4ee1bd982398515a546b056be7b8a6 Mon Sep 17 00:00:00 2001 From: Nathan Page Date: Sun, 7 Feb 2021 19:40:27 -0800 Subject: [PATCH 4/5] use tb_offset to correctly format stack trace --- rich/traceback.py | 51 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/rich/traceback.py b/rich/traceback.py index 757737a0..e8a10362 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -88,27 +88,52 @@ def install( ) ) - try: # pragma: no cover - ip = get_ipython() # type: ignore + def ipy_excepthook_closure(ip) -> None: + tb_data = {} # store information about showtraceback call + default_showtraceback = ip.showtraceback # keep reference of default traceback - def ipy_excepthook(is_syntax: bool = False) -> None: + def ipy_show_traceback(*args, **kwargs) -> None: + """wrap the default ip.showtraceback to store info for ip._showtraceback""" + nonlocal tb_data + tb_data = kwargs + default_showtraceback(*args, **kwargs) + + def ipy_display_traceback(*args, is_syntax: bool = False, **kwargs) -> None: + """Internally called traceback from ip._showtraceback""" + nonlocal tb_data exc_tuple = ip._get_exc_info() - # syntax errors will show no traceback - if is_syntax: - exc_tuple = exc_tuple[0], exc_tuple[1], None - else: - # remove traceback of ipython exec call - exc_tuple = exc_tuple[0], exc_tuple[1], exc_tuple[2].tb_next - excepthook(*exc_tuple) # type: ignore + # do not display trace on syntax error + tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2] + + # determine correct tb_offset + compiled = tb_data.get("running_compiled_code", False) + tb_offset = tb_data.get("tb_offset", 1 if compiled else 0) + # remove ipython internal frames from trace with tb_offset + for _ in range(tb_offset): + if tb is None: + break + tb = tb.tb_next + + excepthook(exc_tuple[0], exc_tuple[1], tb) + tb_data = {} # clear data upon usage # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work - # this is also what the ipython docs recommends to modify when subcalssing InteractiveShell - ip._showtraceback = lambda *args, **kwargs: ipy_excepthook() - ip.showsyntaxerror = lambda *args, **kwargs: ipy_excepthook(is_syntax=True) + # this is also what the ipython docs recommends to modify when subclassing InteractiveShell + ip._showtraceback = ipy_display_traceback + # add wrapper to capture tb_data + ip.showtraceback = ipy_show_traceback + ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback( + *args, is_syntax=True, **kwargs + ) + try: # pragma: no cover + # if wihin ipython, use customized traceback + ip = get_ipython() # type: ignore + ipy_excepthook_closure(ip) return sys.excepthook except Exception: + # otherwise use default system hook old_excepthook = sys.excepthook sys.excepthook = excepthook return old_excepthook From e2f1584bc0c8a9f4ee1af4b43fbd08f709c5f701 Mon Sep 17 00:00:00 2001 From: Nathan Page Date: Sun, 7 Feb 2021 19:46:50 -0800 Subject: [PATCH 5/5] remove checking ipython traceback closure --- rich/traceback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rich/traceback.py b/rich/traceback.py index e8a10362..b7540745 100644 --- a/rich/traceback.py +++ b/rich/traceback.py @@ -88,7 +88,7 @@ def install( ) ) - def ipy_excepthook_closure(ip) -> None: + def ipy_excepthook_closure(ip) -> None: # pragma: no cover tb_data = {} # store information about showtraceback call default_showtraceback = ip.showtraceback # keep reference of default traceback