Skip to content

runners ¤

Functions to run commands and capture output.

RunResult ¤

RunResult(code: int, output: str)

Placeholder for a run result.

Parameters:

  • code (int) –

    The exit code of the command.

  • output (str) –

    The output of the command.

Source code in src/failprint/runners.py
31
32
33
34
35
36
37
38
39
def __init__(self, code: int, output: str) -> None:
    """Initialize the object.

    Arguments:
        code: The exit code of the command.
        output: The output of the command.
    """
    self.code = code
    self.output = output

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)

run_command ¤

run_command(
    cmd: CmdType,
    *,
    capture: Capture = Capture.BOTH,
    ansi: bool = False,
    pty: bool = False,
    stdin: str | None = None
) -> tuple[int, str]

Run a command.

Parameters:

  • cmd (CmdType) –

    The command to run.

  • capture (Capture, default: BOTH ) –

    The output to capture.

  • ansi (bool, default: False ) –

    Whether to accept ANSI sequences.

  • pty (bool, default: False ) –

    Whether to run in a PTY.

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

    String to use as standard input.

Returns:

  • tuple[int, str]

    The exit code and the command output.

Source code in src/failprint/runners.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def run_command(
    cmd: CmdType,
    *,
    capture: Capture = Capture.BOTH,
    ansi: bool = False,
    pty: bool = False,
    stdin: str | None = None,
) -> tuple[int, str]:
    """Run a command.

    Arguments:
        cmd: The command to run.
        capture: The output to capture.
        ansi: Whether to accept ANSI sequences.
        pty: Whether to run in a PTY.
        stdin: String to use as standard input.

    Returns:
        The exit code and the command output.
    """
    shell = isinstance(cmd, str)

    # if chosen format doesn't accept ansi, or on Windows, don't use pty
    if pty and (not ansi or WINDOWS):
        pty = False

    # pty can only combine, so only use pty when combining
    if pty and capture in {Capture.BOTH, Capture.NONE}:
        if shell:
            cmd = ["sh", "-c", cmd]  # type: ignore[list-item]  # we know cmd is str
        return run_pty_subprocess(cmd, capture=capture, stdin=stdin)  # type: ignore[arg-type]  # we made sure cmd is a list

    # we are on Windows
    if WINDOWS:
        # make sure the process can find the executable
        if not shell:
            cmd[0] = shutil.which(cmd[0]) or cmd[0]  # type: ignore[index]  # we know cmd is a list
        return run_subprocess(cmd, capture=capture, shell=shell, stdin=stdin)

    return run_subprocess(cmd, capture=capture, shell=shell, stdin=stdin)

run_function ¤

run_function(
    func: Callable,
    *,
    args: Sequence | None = None,
    kwargs: dict | None = None,
    capture: Capture = Capture.BOTH,
    stdin: str | None = None
) -> tuple[int, str]

Run a function.

Parameters:

  • func (Callable) –

    The function to run.

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

    Positional arguments passed to the function.

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

    Keyword arguments passed to the function.

  • capture (Capture, default: BOTH ) –

    The output to capture.

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

    String to use as standard input.

Returns:

  • tuple[int, str]

    The exit code and the function output.

Source code in src/failprint/runners.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def run_function(
    func: Callable,
    *,
    args: Sequence | None = None,
    kwargs: dict | None = None,
    capture: Capture = Capture.BOTH,
    stdin: str | None = None,
) -> tuple[int, str]:
    """Run a function.

    Arguments:
        func: The function to run.
        args: Positional arguments passed to the function.
        kwargs: Keyword arguments passed to the function.
        capture: The output to capture.
        stdin: String to use as standard input.

    Returns:
        The exit code and the function output.
    """
    args = args or []
    kwargs = kwargs or {}

    if capture == Capture.NONE:
        return run_function_get_code(func, args=args, kwargs=kwargs), ""

    with capture.here(stdin=stdin) as captured:
        code = run_function_get_code(func, args=args, kwargs=kwargs)

    return code, str(captured)

run_function_get_code ¤

run_function_get_code(
    func: Callable, *, args: Sequence, kwargs: dict
) -> int

Run a function and return a exit code.

Parameters:

  • func (Callable) –

    The function to run.

  • args (Sequence) –

    Positional arguments passed to the function.

  • kwargs (dict) –

    Keyword arguments passed to the function.

Returns:

  • int

    An exit code.

Source code in src/failprint/runners.py
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def run_function_get_code(
    func: Callable,
    *,
    args: Sequence,
    kwargs: dict,
) -> int:
    """Run a function and return a exit code.

    Arguments:
        func: The function to run.
        args: Positional arguments passed to the function.
        kwargs: Keyword arguments passed to the function.

    Returns:
        An exit code.
    """
    try:
        result = func(*args, **kwargs)
    except SystemExit as exit:
        if exit.code is None:
            return 0
        if isinstance(exit.code, int):
            return exit.code
        sys.stderr.write(str(exit.code))
        return 1
    except Exception:  # noqa: BLE001
        sys.stderr.write(traceback.format_exc() + "\n")
        return 1

    # if func was a lazy callable, recurse
    if isinstance(result, LazyCallable):
        return run_function_get_code(result, args=(), kwargs={})

    # first check True and False
    # because int(True) == 1 and int(False) == 0
    if result is True:
        return 0
    if result is False:
        return 1
    try:
        return int(result)
    except (ValueError, TypeError):
        if result is None or bool(result):
            return 0
        return 1