Merge pull request #27753 from frappe/feat/add-friedly-error-handler-on-cli

feat: friendly error wrapper on cli
This commit is contained in:
David Arnold 2024-09-13 17:20:29 +02:00 committed by GitHub
commit a46b94527b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 42 deletions

View file

@ -27,13 +27,12 @@ def pass_context(f):
try:
ret = f(frappe._dict(ctx.obj), *args, **kwargs)
except frappe.exceptions.SiteNotSpecifiedError as e:
click.secho(str(e), fg="yellow")
sys.exit(1)
except frappe.exceptions.IncorrectSitePath:
site = ctx.obj.get("sites", "")[0]
click.secho(f"Site {site} does not exist!", fg="yellow")
sys.exit(1)
except (
frappe.exceptions.SiteNotSpecifiedError,
frappe.exceptions.IncorrectSitePath,
frappe.exceptions.CommandFailedError,
) as e:
raise click.UsageError(e, ctx) from e
if profile:
pr.disable()

View file

@ -68,29 +68,20 @@ def debug_on(*exceptions):
return f(*args, **kwargs)
except exceptions as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Pretty print the exception
print("\n\033[91m" + "=" * 60 + "\033[0m") # Red line
print("\033[93m" + str(exc_type.__name__) + ": " + str(exc_value) + "\033[0m")
print("\033[91m" + "=" * 60 + "\033[0m") # Red line
traceback.print_exception(exc_type, exc_value, exc_traceback)
# Print the formatted traceback
traceback_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
for line in traceback_lines:
print("\033[96m" + line.rstrip() + "\033[0m") # Cyan color
# Find the most relevant traceback frame
apps_path = frappe.get_app_path("frappe", "..", "..")
tb = exc_traceback.tb_next
target_frame = None
while tb:
if tb.tb_frame.f_code.co_filename.startswith(apps_path):
target_frame = tb
break
tb = tb.tb_next
if target_frame:
print(f"Starting debugger in: {target_frame.tb_frame.f_code.co_filename}:{tb.tb_lineno}")
p = pdb.Pdb()
p.reset()
p.interaction(target_frame.tb_frame, None)
else:
print("Could not find a suitable traceback frame, using the last frame.")
pdb.post_mortem(exc_traceback)
print("\033[91m" + "=" * 60 + "\033[0m") # Red line
print("\033[92mEntering post-mortem debugging\033[0m")
print("\033[91m" + "=" * 60 + "\033[0m") # Red line
pdb.post_mortem()
raise e

View file

@ -1,6 +1,8 @@
import importlib
import json
import linecache
import os
import sys
import traceback
import warnings
from textwrap import dedent
@ -13,25 +15,85 @@ import frappe.utils
click.disable_unicode_literals_warning = True
class FrappeCommandGroup(click.Group):
def get_command(self, ctx, cmd_name):
rv = super().get_command(ctx, cmd_name)
if rv is not None:
return rv
def FrappeClickWrapper(cls, handler):
class Cls(cls):
# only implemented on groups
def get_command(self, ctx, cmd_name):
rv = super().get_command(ctx, cmd_name)
if rv is not None:
return rv
all_commands = self.list_commands(ctx)
from difflib import get_close_matches
all_commands = self.list_commands(ctx)
from difflib import get_close_matches
possibilities = get_close_matches(cmd_name, all_commands)
raise click.NoSuchOption(
cmd_name, possibilities=possibilities, message=f"No such command: {cmd_name}."
)
possibilities = get_close_matches(cmd_name, all_commands)
raise click.NoSuchOption(
cmd_name, possibilities=possibilities, message=f"No such command: {cmd_name}."
)
def make_context(self, info_name, args, parent=None, **extra):
try:
return super().make_context(info_name, args, parent=parent, **extra)
except click.ClickException as e:
raise e
except Exception as exc:
# call the handler
handler(self, info_name, exc)
sys.exit(1)
def invoke(self, ctx):
try:
return super().invoke(ctx)
except click.ClickException as e:
raise e
except Exception as exc:
# call the handler
handler(self, ctx.info_name, exc)
sys.exit(1)
return Cls
def handle_exception(cmd, info_name, exc):
tb = sys.exc_info()[2]
while tb.tb_next:
tb = tb.tb_next
frame = tb.tb_frame
filename = frame.f_code.co_filename
lineno = frame.f_lineno
(
click.secho("\n:: ", nl=False),
click.secho(f"{exc}", fg="red", bold=True, nl=False),
click.secho(" ::"),
)
click.secho("\nContext:", fg="yellow", bold=True)
click.secho(f" File '{filename}', line {lineno}\n")
context_lines = 5
start = max(1, lineno - context_lines)
end = lineno + context_lines + 1
for i in range(start, end):
line = linecache.getline(filename, i).rstrip()
if i == lineno:
click.secho(f"{i:4d}> {line}", fg="red")
else:
click.echo(f"{i:4d}: {line}")
show_exception = (not sys.stdout.isatty()) or click.confirm(
"\nDo you want to see the full traceback?", default=False
)
if show_exception:
click.secho("\nFull traceback:", fg="red")
click.echo(traceback.format_exc())
click.echo(exc)
def main():
commands = get_app_groups()
commands.update({"get-frappe-commands": get_frappe_commands, "get-frappe-help": get_frappe_help})
FrappeCommandGroup(commands=commands)(prog_name="bench")
FrappeClickWrapper(click.Group, handle_exception)(commands=commands)(prog_name="bench")
def get_app_groups() -> dict[str, click.Group]:
@ -41,12 +103,18 @@ def get_app_groups() -> dict[str, click.Group]:
for app in get_apps():
if app_commands := get_app_commands(app):
commands |= app_commands
return dict(frappe=click.group(name="frappe", commands=commands, cls=FrappeCommandGroup)(app_group))
return dict(
frappe=click.group(
name="frappe", commands=commands, cls=FrappeClickWrapper(click.Group, handle_exception)
)(app_group)
)
def get_app_group(app: str) -> click.Group:
if app_commands := get_app_commands(app):
return click.group(name=app, commands=app_commands)(app_group)
return click.group(
name=app, commands=app_commands, cls=FrappeClickWrapper(click.Group, handle_exception)
)(app_group)
@click.option("--site")

View file

@ -109,6 +109,7 @@ watchdog = "~=3.0.0"
hypothesis = "~=6.77.0"
responses = "==0.23.1"
freezegun = "~=1.2.2"
pdbpp = "~=0.10.3"
[tool.ruff]
line-length = 110