Skip to content

Commit 273bc5c

Browse files
committed
content: stdout vs stderr
1 parent 79730fc commit 273bc5c

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

docs/stdout-vs-stderr.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
tags:
3+
- interface
4+
---
5+
6+
## Overview
7+
8+
When code prints things, it actually writes to one of two virtual files: "standard output" or "standard error".
9+
10+
* For a human user, there's usually no difference: Both `stdout` and `stderr` are visible on the terminal.
11+
* For machines, there's a difference: A script consuming the output of your script "sees" only `stdout`.
12+
13+
Distinguishing between `stdout` and `stderr`, is one of the [Basics recommended by the CLIG](https://clig.dev/#the-basics)
14+
15+
TLDR
16+
17+
* Send output that can be piped and consumed by a next script to `stdout`.
18+
* Send everything else, especially errors and log messages to `stderr`.
19+
20+
For more explanation, on `stdout` and `stderr`, see also the [Typer Docs](https://typer.tiangolo.com/tutorial/printing/#standard-output-and-standard-error)
21+
22+
## Printing to `stdout` vs `stderr` with Rich
23+
24+
In Rich, you use a `rich.console.Console` object for printing.
25+
26+
* By default, it prints to `stdout`.
27+
* You can initialize the `Console` with `stderr=True`, so that it prints to `stderr`.
28+
29+
After creating a talk, print:
30+
31+
* the talk ID to `stdout`
32+
* a logging message to `stderr`
33+
34+
```python
35+
from rich.console import Console
36+
37+
stdout_console = Console()
38+
stderr_console = Console(stderr=True)
39+
40+
stdout_console.print(talk.talk_id)
41+
stderr_console.print("New talk created. 🪅")
42+
```
43+
44+
## Testing
45+
46+
For tests with `typer.testing.CliRunner`, it's recommended to verify the content of `stdout` and `stdin` separately.
47+
48+
In order to do that, you need to initialize the `typer.testing.CliRunner` instance with `mix_stderr=False`.
49+
50+
```python
51+
runner = CliRunner(mix_stderr=False)
52+
```
53+
54+
## Iteration vs Keeping the Interface Stable
55+
56+
> Changing output for humans is usually OK. The only way to make an interface easy to use is to iterate on it, and if the output is considered an interface, then you can’t iterate on it. Encourage your users to use --plain or --json in scripts to keep output stable (see Output).
57+
58+
[CLIG Guidelines / Future-proofing](https://clig.dev/#future-proofing)
59+
60+
## More Info
61+
62+
* [Typer Docs / Printing and Colors / "Standard Output" and "Standard Error"](https://typer.tiangolo.com/tutorial/printing/#standard-output-and-standard-error)
63+
* [CLIG Guidelines / Basics](https://clig.dev/#the-basics)
64+
* [CLIG Guidelines / Future-proofing](https://clig.dev/#future-proofing)

docs/typer-testing-clirunner.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ By default, `CliRunner` is created with `mix_stderr=True`. This means, that the
2424

2525
Recommended practice: Verify separately the content of `stdout` and `stderr`. => Create the `CliRunner` instance with `mix_stderr=False`
2626

27+
Verifying the content of `stdout` and `stderr` separately after invoking a command:
28+
29+
```python
30+
from pycon_catalogue.cli.cli import app
31+
32+
runner = CliRunner(mix_stderr=False)
33+
34+
def test_success(cmd_parts):
35+
result = runner.invoke(app, "create", "--name", "something")
36+
37+
assert result.exit_code == 0
38+
assert result.stdout
39+
# stderr
40+
assert "New talk created. 🪅" in result.stderr
41+
```
42+
2743
[Typer docs](https://typer.tiangolo.com/tutorial/testing/#check-the-result)
2844

2945
## Invoke: Use only string Arguments

0 commit comments

Comments
 (0)