Let's start with the current behaviour of CliRunner regarding its mix_stderr option.
Here is a simple CLI:
from click import command, echo
from click.testing import CliRunner
@command()
def hello():
echo("1 - Hello world")
echo("2 - Hello error", err=True)
echo("3 - Good bye world")
echo("4 - Good bye error", err=True)
if __name__ == "__main__":
print("\n*** mix_stderr=True (default) ***\n")
runner = CliRunner(mix_stderr=True)
result = runner.invoke(hello)
print(f"--- <stdout> ---\n{result.stdout}")
print(f"--- <stderr> ---\n(raises error)\n")
print(f"--- <output> ---\n{result.output}")
print("\n*** mix_stderr=False ***\n")
runner = CliRunner(mix_stderr=False)
result = runner.invoke(hello)
print(f"--- <stdout> ---\n{result.stdout}")
print(f"--- <stderr> ---\n{result.stderr}")
print(f"--- <output> ---\n{result.output}")
When called, the CLI above produces:
$ python ./cli_runner_stderr_test.py
*** mix_stderr=True (default) ***
--- <stdout> ---
1 - Hello world
2 - Hello error
3 - Good bye world
4 - Good bye error
--- <stderr> ---
(raises error)
--- <output> ---
1 - Hello world
2 - Hello error
3 - Good bye world
4 - Good bye error
*** mix_stderr=False ***
--- <stdout> ---
1 - Hello world
3 - Good bye world
--- <stderr> ---
2 - Hello error
4 - Good bye error
--- <output> ---
1 - Hello world
3 - Good bye world
What I'd like to propose is to instead have CliRunner produce this result:
$ python ./cli_runner_stderr_test.py
*** mix_stderr=True (default) ***
--- <stdout> ---
1 - Hello world
3 - Good bye world
--- <stderr> ---
2 - Hello error
4 - Good bye error
--- <output> ---
1 - Hello world
2 - Hello error
3 - Good bye world
4 - Good bye error
*** mix_stderr=False ***
--- <stdout> ---
1 - Hello world
3 - Good bye world
--- <stderr> ---
2 - Hello error
4 - Good bye error
--- <output> ---
1 - Hello world
3 - Good bye world
So what I propose is to streamline the semantics of result.stdout, result.stderr and result.output:
- Let
result.stdout always contain the pure output to <stdout>. Never mangle <stderr> in it.
- Let
result.stderr always contain the pure output to <stderr>. Never raise an error.
- Use
result.output as the content the user is expected to see:
- let it be a proxy of
<stdout> if mix_stderr=False
- have it produce a mix of
<stdout> and <stderr> if mix_stderr=True
The advantage of this is we could properly check the individual <stdout> and <stderr> streams, and check for the user output by inspecting <output>.
The raised exception in <stderr> has been introduced in 7.0 for backward compatibility, because the code prior to 7.0 wasn't returning the stderr/stderr split output (see #868). It's from 2017 so we can safely change that behaviour by now.
Note that I explicitely numbered each echo() statement to highlight the print order. If a refactor is attempted, I am anticipating some edge-cases around the preservation of order, so it's good to have simple code to test that.
Let's start with the current behaviour of
CliRunnerregarding itsmix_stderroption.Here is a simple CLI:
When called, the CLI above produces:
What I'd like to propose is to instead have
CliRunnerproduce this result:So what I propose is to streamline the semantics of
result.stdout,result.stderrandresult.output:result.stdoutalways contain the pure output to<stdout>. Never mangle<stderr>in it.result.stderralways contain the pure output to<stderr>. Never raise an error.result.outputas the content the user is expected to see:<stdout>ifmix_stderr=False<stdout>and<stderr>ifmix_stderr=TrueThe advantage of this is we could properly check the individual
<stdout>and<stderr>streams, and check for the user output by inspecting<output>.The raised exception in
<stderr>has been introduced in7.0for backward compatibility, because the code prior to7.0wasn't returning thestderr/stderrsplit output (see #868). It's from 2017 so we can safely change that behaviour by now.Note that I explicitely numbered each
echo()statement to highlight the print order. If a refactor is attempted, I am anticipating some edge-cases around the preservation of order, so it's good to have simple code to test that.