Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions interfacy/common/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

from stdl.st import colored

from interfacy.exceptions import CommandError


def info(message: str) -> None:
print(message, file=sys.stdout)
Expand Down Expand Up @@ -39,12 +37,14 @@ def log_exception(tag: str, exc: BaseException, *, full_traceback: bool) -> None
]
error("\n".join(["Traceback (most recent call last):", *frames]))

message = colored(
str(exc) if isinstance(exc, CommandError) else type(exc).__name__, color="red"
)
message = colored(type(exc).__name__, color="red")
error(message)


def log_command_error(exc: BaseException) -> None:
error(colored(str(exc), color="red"))


def log_interrupt(*, silent: bool) -> None:
if silent:
return
Expand All @@ -57,6 +57,7 @@ def log_interrupt(*, silent: bool) -> None:
"error",
"info",
"log",
"log_command_error",
"log_error",
"log_exception",
"log_interrupt",
Expand Down
8 changes: 6 additions & 2 deletions interfacy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ class InterfacyError(Exception):
"""Base exception for Interfacy errors."""


class CommandError(InterfacyError):
"""A command failure whose message is safe to display verbatim to the user."""
class CommandError(Exception):
"""Raise from a command for a failure whose message is written for the user.

``run()`` prints the message, without chained causes, and exits with
``ExitCode.COMMAND_FAILED``. Other command exceptions print only their type.
"""


class InterfacyExit(BaseException):
Expand Down
6 changes: 3 additions & 3 deletions interfacy/runtime/invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from inspect import isawaitable
from typing import Any, NoReturn

from interfacy.common.console import error
from interfacy.common.console import error, log_command_error
from interfacy.common.resources import ResourceStack
from interfacy.declarations.executable_flags import ExecutableAction
from interfacy.exceptions import (
Expand Down Expand Up @@ -77,8 +77,6 @@ def __init__(self, code: int, error: Exception) -> None:


def _execution_error_code(error: Exception) -> ExitCode:
if isinstance(error, CommandError):
return ExitCode.COMMAND_FAILED
if isinstance(error, PipeInputError):
return ExitCode.USAGE
if isinstance(error, _CONFIGURATION_ERRORS):
Expand Down Expand Up @@ -148,6 +146,8 @@ def run(self, parse: ParseStep, execute: ExecuteStep) -> NoReturn:
exit_code = rendering_error.code
else:
error(rendered.rstrip())
elif isinstance(failure, CommandError):
log_command_error(failure)
else:
self._policy.log_exception(failure)
else:
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/runtime/test_invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ def command() -> None:
)
assert "private note" not in captured.err
assert "private cause" not in captured.err
assert ("Traceback" in captured.err) == full_traceback
assert ("Traceback" in captured.err) == (
full_traceback and not issubclass(failure_type, CommandError)
)


@pytest.mark.parametrize("mode", ["sync", "async"])
Expand Down
Loading