Skip to content

Commit 5ba3676

Browse files
authored
Raise CLI errors in debug mode (python-kasa#771)
1 parent 29e6b92 commit 5ba3676

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
lines changed

kasa/cli.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,43 @@ def wrapper(message=None, *args, **kwargs):
9090
pass_dev = click.make_pass_decorator(Device)
9191

9292

93-
class ExceptionHandlerGroup(click.Group):
94-
"""Group to capture all exceptions and print them nicely.
93+
def CatchAllExceptions(cls):
94+
"""Capture all exceptions and prints them nicely.
9595
96-
Idea from https://stackoverflow.com/a/44347763
96+
Idea from https://stackoverflow.com/a/44347763 and
97+
https://stackoverflow.com/questions/52213375
9798
"""
9899

99-
def __call__(self, *args, **kwargs):
100-
"""Run the coroutine in the event loop and print any exceptions."""
101-
try:
102-
asyncio.get_event_loop().run_until_complete(self.main(*args, **kwargs))
103-
except Exception as ex:
104-
echo(f"Got error: {ex!r}")
100+
def _handle_exception(debug, exc):
101+
if isinstance(exc, click.ClickException):
102+
raise
103+
echo(f"Raised error: {exc}")
104+
if debug:
105105
raise
106+
echo("Run with --debug enabled to see stacktrace")
107+
sys.exit(1)
108+
109+
class _CommandCls(cls):
110+
_debug = False
111+
112+
async def make_context(self, info_name, args, parent=None, **extra):
113+
self._debug = any(
114+
[arg for arg in args if arg in ["--debug", "-d", "--verbose", "-v"]]
115+
)
116+
try:
117+
return await super().make_context(
118+
info_name, args, parent=parent, **extra
119+
)
120+
except Exception as exc:
121+
_handle_exception(self._debug, exc)
122+
123+
async def invoke(self, ctx):
124+
try:
125+
return await super().invoke(ctx)
126+
except Exception as exc:
127+
_handle_exception(self._debug, exc)
128+
129+
return _CommandCls
106130

107131

108132
def json_formatter_cb(result, **kwargs):
@@ -129,7 +153,7 @@ def _device_to_serializable(val: Device):
129153

130154
@click.group(
131155
invoke_without_command=True,
132-
cls=ExceptionHandlerGroup,
156+
cls=CatchAllExceptions(click.Group),
133157
result_callback=json_formatter_cb,
134158
)
135159
@click.option(

kasa/tests/test_cli.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,7 @@ async def test_host_unsupported(unsupported_device_info):
504504
"foo",
505505
"--password",
506506
"bar",
507+
"--debug",
507508
],
508509
)
509510

@@ -563,6 +564,7 @@ async def test_host_auth_failed(discovery_mock, mocker):
563564
"foo",
564565
"--password",
565566
"bar",
567+
"--debug",
566568
],
567569
)
568570

@@ -610,3 +612,49 @@ async def test_shell(dev: Device, mocker):
610612
res = await runner.invoke(cli, ["shell"], obj=dev)
611613
assert res.exit_code == 0
612614
embed.assert_called()
615+
616+
617+
async def test_errors(mocker):
618+
runner = CliRunner()
619+
err = SmartDeviceException("Foobar")
620+
621+
# Test masking
622+
mocker.patch("kasa.Discover.discover", side_effect=err)
623+
res = await runner.invoke(
624+
cli,
625+
["--username", "foo", "--password", "bar"],
626+
)
627+
assert res.exit_code == 1
628+
assert "Raised error: Foobar" in res.output
629+
assert "Run with --debug enabled to see stacktrace" in res.output
630+
assert isinstance(res.exception, SystemExit)
631+
632+
# Test --debug
633+
res = await runner.invoke(
634+
cli,
635+
["--debug"],
636+
)
637+
assert res.exit_code == 1
638+
assert "Raised error: Foobar" in res.output
639+
assert res.exception == err
640+
641+
# Test no device passed to subcommand
642+
mocker.patch("kasa.Discover.discover", return_value={})
643+
res = await runner.invoke(
644+
cli,
645+
["sysinfo"],
646+
)
647+
assert res.exit_code == 1
648+
assert (
649+
"Raised error: Managed to invoke callback without a context object of type 'Device' existing."
650+
in res.output
651+
)
652+
assert isinstance(res.exception, SystemExit)
653+
654+
# Test click error
655+
res = await runner.invoke(
656+
cli,
657+
["--foobar"],
658+
)
659+
assert res.exit_code == 2
660+
assert "Raised error:" not in res.output

0 commit comments

Comments
 (0)