| from __future__ import annotations |
| |
| from dataclasses import dataclass, field |
| import traceback |
| |
| |
| TYPE_CHECKING = False |
| if TYPE_CHECKING: |
| from threading import Thread |
| from types import TracebackType |
| from typing import Protocol |
| |
| class ExceptHookArgs(Protocol): |
| @property |
| def exc_type(self) -> type[BaseException]: ... |
| @property |
| def exc_value(self) -> BaseException | None: ... |
| @property |
| def exc_traceback(self) -> TracebackType | None: ... |
| @property |
| def thread(self) -> Thread | None: ... |
| |
| class ShowExceptions(Protocol): |
| def __call__(self) -> int: ... |
| def add(self, s: str) -> None: ... |
| |
| from .reader import Reader |
| |
| |
| def install_threading_hook(reader: Reader) -> None: |
| import threading |
| |
| @dataclass |
| class ExceptHookHandler: |
| lock: threading.Lock = field(default_factory=threading.Lock) |
| messages: list[str] = field(default_factory=list) |
| |
| def show(self) -> int: |
| count = 0 |
| with self.lock: |
| if not self.messages: |
| return 0 |
| reader.restore() |
| for tb in self.messages: |
| count += 1 |
| if tb: |
| print(tb) |
| self.messages.clear() |
| reader.scheduled_commands.append("ctrl-c") |
| reader.prepare() |
| return count |
| |
| def add(self, s: str) -> None: |
| with self.lock: |
| self.messages.append(s) |
| |
| def exception(self, args: ExceptHookArgs) -> None: |
| lines = traceback.format_exception( |
| args.exc_type, |
| args.exc_value, |
| args.exc_traceback, |
| colorize=reader.can_colorize, |
| ) # type: ignore[call-overload] |
| pre = f"\nException in {args.thread.name}:\n" if args.thread else "\n" |
| tb = pre + "".join(lines) |
| self.add(tb) |
| |
| def __call__(self) -> int: |
| return self.show() |
| |
| |
| handler = ExceptHookHandler() |
| reader.threading_hook = handler |
| threading.excepthook = handler.exception |