tracebacks of event handlers are now colorful

This commit is contained in:
Aran-Fey
2025-02-07 21:04:48 +01:00
parent 13c3e2bc3f
commit cddc7b94aa
5 changed files with 53 additions and 56 deletions

View File

@@ -1,9 +1,6 @@
import functools
import html
import itertools
import os
import sys
import traceback
import types
import typing as t
from pathlib import Path
@@ -14,8 +11,7 @@ import rio
import rio.app_server.fastapi_server
import rio.global_state
from ... import icon_registry, project_config
from .. import nice_traceback
from ... import icon_registry, nice_traceback, project_config
class AppLoadError(Exception):
@@ -27,24 +23,6 @@ class AppLoadError(Exception):
return self.args[0]
def remove_rio_internals_from_traceback(
tb: list[traceback.FrameSummary],
) -> t.Sequence[traceback.FrameSummary]:
# Skip frames which are internal to rio (or libraries used by rio) until we
# hit the first non-rio frame. Then include everything.
rio_root = rio.__file__
assert rio_root.endswith(os.sep + "__init__.py")
rio_root = rio_root.removesuffix(os.sep + "__init__.py")
def predicate(frame: traceback.FrameSummary) -> bool:
return (
frame.filename.startswith((rio_root, "<frozen importlib"))
or frame.filename == path_imports.__file__
)
return list(itertools.dropwhile(predicate, tb))
def make_traceback_html(
*,
err: t.Union[str, BaseException],
@@ -58,7 +36,6 @@ def make_traceback_html(
traceback_html = nice_traceback.format_exception_html(
err,
relpath=project_directory,
preprocess_traceback=remove_rio_internals_from_traceback,
)
return f"""

View File

@@ -19,9 +19,8 @@ import rio.arequests as arequests
import rio.cli
import rio.snippets
from ... import project_config, utils, version
from ... import nice_traceback, project_config, utils, version
from ...debug.monkeypatches import apply_monkeypatches
from .. import nice_traceback
from . import (
app_loading,
file_watcher_worker,
@@ -306,12 +305,9 @@ class Arbiter:
revel.error(f"The app could not be loaded: {err}")
if err.__cause__ is not None:
revel.print(
nice_traceback.format_exception_revel(
err.__cause__,
relpath=self.proj.project_directory,
preprocess_traceback=app_loading.remove_rio_internals_from_traceback,
)
nice_traceback.print_exception(
err.__cause__,
relpath=self.proj.project_directory,
)
# If running in release mode, no further attempts to load the app
@@ -454,7 +450,7 @@ class Arbiter:
revel.error("The arbiter has crashed.")
revel.error("This is a bug in Rio - please report it")
print()
revel.print(nice_traceback.format_exception_revel(err))
nice_traceback.print_exception(err)
rio.cli._logger.exception("The arbiter has crashed")

View File

@@ -11,8 +11,7 @@ import rio
import rio.app_server.fastapi_server
import rio.cli
from ... import utils
from .. import nice_traceback
from ... import nice_traceback, utils
from . import run_models
@@ -110,8 +109,7 @@ class UvicornWorker:
rio.cli._logger.exception(f"Uvicorn has crashed")
revel.error(f"Uvicorn has crashed:")
print()
revel.print(nice_traceback.format_exception_revel(err))
nice_traceback.print_exception(err)
self.push_event(run_models.StopRequested())
finally:
rio.cli._logger.debug("Requesting uvicorn to exit")

View File

@@ -6,14 +6,33 @@ but is colored and just tweaked in general.
import dataclasses
import html
import io
import itertools
import linecache
import os
import sys
import traceback
import typing as t
from pathlib import Path
import path_imports
import revel
import rio
__all__ = [
"format_exception_revel",
"format_exception_html",
"FormatStyle",
"print_exception",
]
def print_exception(
error: BaseException, *, relpath: Path | None = None
) -> None:
text = format_exception_revel(error, relpath=relpath)
revel.print(text)
@dataclasses.dataclass
class FormatStyle:
@@ -60,6 +79,24 @@ def _handle_syntax_error(err: SyntaxError) -> traceback.FrameSummary:
)
def remove_rio_internals_from_traceback(
tb: list[traceback.FrameSummary],
) -> t.Sequence[traceback.FrameSummary]:
# Skip frames which are internal to rio (or libraries used by rio) until we
# hit the first non-rio frame. Then include everything.
rio_root = rio.__file__
assert rio_root.endswith(os.sep + "__init__.py")
rio_root = rio_root.removesuffix(os.sep + "__init__.py")
def predicate(frame: traceback.FrameSummary) -> bool:
return (
frame.filename.startswith((rio_root, "<frozen importlib"))
or frame.filename == path_imports.__file__
)
return list(itertools.dropwhile(predicate, tb))
def _format_single_exception_raw(
out: t.IO[str],
err: BaseException,
@@ -67,9 +104,6 @@ def _format_single_exception_raw(
include_header: bool,
style: FormatStyle,
relpath: Path | None,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
],
) -> None:
"""
Format a single exception and write it to the output stream.
@@ -81,7 +115,7 @@ def _format_single_exception_raw(
if isinstance(err, SyntaxError):
tb_list.append(_handle_syntax_error(err))
tb_list = preprocess_traceback(tb_list)
tb_list = remove_rio_internals_from_traceback(tb_list)
# TODO: Add special handling for recursion errors. Instead of printing the
# same frame 1000 times, print a message like "Last 5 frames repeated 200
@@ -164,9 +198,6 @@ def format_exception_raw(
*,
style: FormatStyle,
relpath: Path | None = None,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception into a pretty string with the given style.
@@ -198,7 +229,6 @@ def format_exception_raw(
include_header=include_header,
style=style,
relpath=relpath,
preprocess_traceback=preprocess_traceback,
)
# Format
@@ -211,9 +241,6 @@ def format_exception_revel(
err: BaseException,
*,
relpath: Path | None = None,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception using revel's styling.
@@ -237,7 +264,6 @@ def format_exception_revel(
err,
style=style,
relpath=relpath,
preprocess_traceback=preprocess_traceback,
)
@@ -245,9 +271,6 @@ def format_exception_html(
err: BaseException,
*,
relpath: Path | None = None,
preprocess_traceback: t.Callable[
[list[traceback.FrameSummary]], t.Sequence[traceback.FrameSummary]
] = lambda tb: tb,
) -> str:
"""
Format an exception into HTML with appropriate styling.
@@ -271,7 +294,6 @@ def format_exception_html(
err,
style=style,
relpath=relpath,
preprocess_traceback=preprocess_traceback,
)
# HTML-ify newlines

View File

@@ -16,7 +16,9 @@ import typing as t
import weakref
from datetime import tzinfo
import introspection
import ordered_set
import revel
import starlette.datastructures
import unicall
import uniserde
@@ -33,6 +35,7 @@ from . import (
fills,
global_state,
inspection,
nice_traceback,
routing,
serialization,
session_attachments,
@@ -864,9 +867,9 @@ window.resizeTo(screen.availWidth, screen.availHeight);
await result
# Display and discard exceptions
except Exception:
print("Exception in event handler:")
traceback.print_exc()
except Exception as error:
revel.error("Exception in event handler:")
nice_traceback.print_exception(error)
if refresh:
await self._refresh()
@@ -2318,6 +2321,7 @@ window.history.{method}(null, "", {json.dumps(relative_url)})
new_name="file_types",
)
@deprecations.deprecated(since="0.10", replacement=pick_file)
@introspection.set_signature(pick_file)
async def file_chooser(
self,
*args,