Merge pull request #27753 from frappe/feat/add-friedly-error-handler-on-cli
feat: friendly error wrapper on cli
This commit is contained in:
commit
a46b94527b
4 changed files with 101 additions and 42 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue