Skip to content

failprint ¤

failprint package.

Run a command, print its output only if it fails.

Capture ¤

Bases: Enum

An enum to store the different possible output types.

cast classmethod ¤

cast(value: str | bool | Capture | None) -> Capture

Cast a value to an actual Capture enumeration value.

Parameters:

Returns:

  • Capture

    A Capture enumeration value.

Source code in src/failprint/capture.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@classmethod
def cast(cls, value: str | bool | Capture | None) -> Capture:
    """Cast a value to an actual Capture enumeration value.

    Arguments:
        value: The value to cast.

    Returns:
        A Capture enumeration value.
    """
    if value is None:
        return cls.BOTH
    if value is True:
        return cls.BOTH
    if value is False:
        return cls.NONE
    if isinstance(value, cls):
        return value
    # consider it's a string
    # let potential errors bubble up
    return cls(value)

here ¤

here(stdin: str | None = None) -> Iterator[CaptureManager]

Context manager to capture standard output/error.

Parameters:

  • stdin (str | None, default: None ) –

    Optional input.

Yields:

Examples:

>>> def print_things() -> None:
...     print("1")
...     sys.stderr.write("2\n")
...     os.system("echo 3")
...     subprocess.run(["sh", "-c", "echo 4 >&2"])
>>> with Capture.BOTH.here() as captured:
...     print_things()
... print(captured)
1
2
3
4
Source code in src/failprint/capture.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@contextmanager
def here(self, stdin: str | None = None) -> Iterator[CaptureManager]:
    """Context manager to capture standard output/error.

    Parameters:
        stdin: Optional input.

    Yields:
        A lazy string with the captured contents.

    Examples:
        >>> def print_things() -> None:
        ...     print("1")
        ...     sys.stderr.write("2\\n")
        ...     os.system("echo 3")
        ...     subprocess.run(["sh", "-c", "echo 4 >&2"])
        >>> with Capture.BOTH.here() as captured:
        ...     print_things()
        ... print(captured)
        1
        2
        3
        4
    """  # noqa: D301
    with CaptureManager(self, stdin=stdin) as captured:
        yield captured

CaptureManager ¤

CaptureManager(
    capture: Capture = Capture.BOTH,
    stdin: str | None = None,
)

Context manager to capture standard output and error at the file descriptor level.

Usable directly through Capture.here.

Examples:

>>> def print_things() -> None:
...     print("1")
...     sys.stderr.write("2\n")
...     os.system("echo 3")
...     subprocess.run(["sh", "-c", "echo 4 >&2"])
>>> with CaptureManager(Capture.BOTH) as captured:
...     print_things()
... print(captured)
1
2
3
4

Parameters:

  • capture (Capture, default: BOTH ) –

    What to capture.

  • stdin (str | None, default: None ) –

    Optional input.

Source code in src/failprint/capture.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(self, capture: Capture = Capture.BOTH, stdin: str | None = None) -> None:
    """Initialize the context manager.

    Parameters:
        capture: What to capture.
        stdin: Optional input.
    """
    self._temp_file: IO[str] | None = None
    self._capture = capture
    self._devnull: TextIO | None = None
    self._stdin = stdin
    self._saved_stdin: TextIO | None = None
    self._stdout_fd: int = -1
    self._stderr_fd: int = -1
    self._saved_stdout_fd: int = -1
    self._saved_stderr_fd: int = -1
    self._output: str | None = None

output property ¤

output: str

Captured output.

Raises:

  • RuntimeError

    When accessing captured output before exiting the context manager.

Format ¤

Format(
    template: str,
    *,
    progress_template: str | None = None,
    accept_ansi: bool = True
)

Class to define a display format.

Parameters:

  • template (str) –

    The main template.

  • progress_template (str | None, default: None ) –

    The template to show progress.

  • accept_ansi (bool, default: True ) –

    Whether to accept ANSI sequences.

Source code in src/failprint/formats.py
49
50
51
52
53
54
55
56
57
58
59
def __init__(self, template: str, *, progress_template: str | None = None, accept_ansi: bool = True) -> None:
    """Initialize the object.

    Arguments:
        template: The main template.
        progress_template: The template to show progress.
        accept_ansi: Whether to accept ANSI sequences.
    """
    self.template = template
    self.progress_template = progress_template
    self.accept_ansi = accept_ansi

LazyCallable ¤

LazyCallable(
    call: Callable[_P, _R],
    args: tuple,
    kwargs: dict,
    name: str | None = None,
)

Bases: Generic[_R]

This class allows users to create and pass lazy callables to the runner.

Parameters:

  • call (Callable[_P, _R]) –

    The origin callable.

  • args (tuple) –

    The *args to pass when calling.

  • kwargs (dict) –

    The **kwargs to pass when calling.

  • name (str | None, default: None ) –

    The name of the callable.

Source code in src/failprint/lazy.py
22
23
24
25
26
27
28
29
30
31
32
33
34
def __init__(self, call: Callable[_P, _R], args: tuple, kwargs: dict, name: str | None = None) -> None:
    """Initialize a lazy callable.

    Parameters:
        call: The origin callable.
        args: The `*args` to pass when calling.
        kwargs: The `**kwargs` to pass when calling.
        name: The name of the callable.
    """
    self.call = call
    self.args = args
    self.kwargs = kwargs
    self.name = name

main ¤

main(args: list[str] | None = None) -> int

Run the main program.

This function is executed when you type failprint or python -m failprint.

Parameters:

  • args (list[str] | None, default: None ) –

    Arguments passed from the command line.

Returns:

  • int

    An exit code.

Source code in src/failprint/cli.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def main(args: list[str] | None = None) -> int:
    """Run the main program.

    This function is executed when you type `failprint` or `python -m failprint`.

    Parameters:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    parser = get_parser()
    opts = parser.parse_args(args).__dict__.items()
    return run(**{_: value for _, value in opts if value is not None}).code

run ¤

run(
    cmd: CmdFuncType,
    *,
    args: Sequence | None = None,
    kwargs: dict | None = None,
    number: int = 1,
    capture: str | bool | Capture | None = None,
    title: str | None = None,
    fmt: str | None = None,
    pty: bool = False,
    progress: bool = True,
    nofail: bool = False,
    quiet: bool = False,
    silent: bool = False,
    stdin: str | None = None,
    command: str | None = None
) -> RunResult

Run a command in a subprocess or a Python function, and print its output if it fails.

Parameters:

  • cmd (CmdFuncType) –

    The command to run.

  • args (Sequence | None, default: None ) –

    Arguments to pass to the callable.

  • kwargs (dict | None, default: None ) –

    Keyword arguments to pass to the callable.

  • number (int, default: 1 ) –

    The command number.

  • capture (str | bool | Capture | None, default: None ) –

    The output to capture.

  • title (str | None, default: None ) –

    The command title.

  • fmt (str | None, default: None ) –

    The output format.

  • pty (bool, default: False ) –

    Whether to run in a PTY.

  • progress (bool, default: True ) –

    Whether to show progress.

  • nofail (bool, default: False ) –

    Whether to always succeed.

  • quiet (bool, default: False ) –

    Whether to not print the command output.

  • silent (bool, default: False ) –

    Don't print anything.

  • stdin (str | None, default: None ) –

    String to use as standard input.

  • command (str | None, default: None ) –

    The command to display.

Returns:

  • RunResult

    The command exit code, or 0 if nofail is True.

Source code in src/failprint/runners.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def run(
    cmd: CmdFuncType,
    *,
    args: Sequence | None = None,
    kwargs: dict | None = None,
    number: int = 1,
    capture: str | bool | Capture | None = None,
    title: str | None = None,
    fmt: str | None = None,
    pty: bool = False,
    progress: bool = True,
    nofail: bool = False,
    quiet: bool = False,
    silent: bool = False,
    stdin: str | None = None,
    command: str | None = None,
) -> RunResult:
    """Run a command in a subprocess or a Python function, and print its output if it fails.

    Arguments:
        cmd: The command to run.
        args: Arguments to pass to the callable.
        kwargs: Keyword arguments to pass to the callable.
        number: The command number.
        capture: The output to capture.
        title: The command title.
        fmt: The output format.
        pty: Whether to run in a PTY.
        progress: Whether to show progress.
        nofail: Whether to always succeed.
        quiet: Whether to not print the command output.
        silent: Don't print anything.
        stdin: String to use as standard input.
        command: The command to display.

    Returns:
        The command exit code, or 0 if `nofail` is True.
    """
    format_name: str = fmt or os.environ.get("FAILPRINT_FORMAT", DEFAULT_FORMAT)  # type: ignore[assignment]
    format_name = accept_custom_format(format_name)
    format_obj = formats.get(format_name, formats[DEFAULT_FORMAT])

    env = Environment(autoescape=False)  # noqa: S701 (no HTML: no need to escape)
    env.filters["indent"] = textwrap.indent
    env.filters["escape"] = env.filters["e"] = escape
    env.filters["unescape"] = env.filters["u"] = unescape

    command = command if command is not None else printable_command(cmd, args, kwargs)

    if not silent and progress and format_obj.progress_template:
        progress_template = env.from_string(format_obj.progress_template)
        print(unescape(parse(progress_template.render({"title": title, "command": command}))), end="\r")  # noqa: T201

    capture = Capture.cast(capture)

    if callable(cmd):
        code, output = run_function(cmd, args=args, kwargs=kwargs, capture=capture, stdin=stdin)
    else:
        code, output = run_command(cmd, capture=capture, ansi=format_obj.accept_ansi, pty=pty, stdin=stdin)

    if not silent:
        template = env.from_string(format_obj.template)
        rendered = template.render(
            {
                "title": title,
                "command": command,
                "code": code,
                "success": code == 0,
                "failure": code != 0,
                "number": number,
                "output": output,
                "nofail": nofail,
                "quiet": quiet,
                "silent": silent,
            },
        )
        print(unescape(parse(rendered)))  # noqa: T201

    return RunResult(0 if nofail else code, output)