Pytest plugin providing debug fixtures, ANSI-stripped capsys, whitespace-visible assertions, and terminal column management.
Project description
pytest-devtools
A pytest plugin that smooths over a few common annoyances when writing and debugging tests.
Features
- Debug fixture: pretty-prints variables, paths, and data structures with Rich, and only shows the output when a test fails.
- Stripped
capsysoutput: removes ANSI escape codes (and optionally thetmp_pathprefix) from captured stdout/stderr so assertions stay readable. - Visible whitespace in diffs: replaces tabs, trailing spaces, carriage returns, and newlines with Unicode symbols when an assertion fails.
- Terminal column width control: sets
COLUMNSfor every test so libraries that auto-wrap (Rich, Click, etc.) produce stable output.
Installation
# uv
uv add pytest-devtools
# pip
pip install pytest-devtools
Requirements: Python 3.10+ and pytest 7.0+.
The plugin registers itself through the pytest11 entry point, so no conftest.py changes are needed.
Debug Fixture
The debug fixture is a callable that pretty-prints any Python object using Rich. Output is buffered during the test and written to stderr only if the test fails (or always, with --print-debug).
Basic Usage
def test_user_creation(debug, tmp_path):
user = {"name": "Alice", "roles": ["admin", "editor"]}
debug(user)
config_path = tmp_path / "config.toml"
config_path.write_text("[settings]\nverbose = true")
debug(config_path)
assert user["name"] == "Alice"
When the test fails, stderr shows the buffered output between rule separators:
──────────────────────────── Debug ─────────────────────────────
{'name': 'Alice', 'roles': ['admin', 'editor']}
──────────────────────────── Debug ─────────────────────────────
Multiple Values and Titles
Pass several arguments in a single call, and use title to label the section:
def test_transform(debug):
before = [1, 2, 3]
after = [x * 2 for x in before]
debug(before, after, title="Transform")
Per-Call Options
Override any option on a single call:
def test_deep_structure(debug, tmp_path):
nested = {"a": {"b": {"c": {"d": "deep"}}}}
# Limit nesting depth
debug(nested, max_depth=2)
# Limit collection length
debug(list(range(100)), max_length=5)
# Show type annotations
debug(nested, show_type=True)
# Show directory tree for Path objects
debug(tmp_path, list_dir_contents=True)
# Disable tmp_path prefix stripping
debug(tmp_path / "output.txt", strip_tmp_path=False)
Path Handling
When you pass a pathlib.Path:
tmp_pathstripping (default: on). If the path is insidetmp_path, only the relative portion is shown. A path like/var/folders/.../pytest-1234/test_foo0/subdir/file.txtdisplays assubdir/file.txt.- Directory listing (default: off). When enabled and the path is a directory, a Rich tree shows the directory contents recursively.
CLI Options
| Flag | Description |
|---|---|
--print-debug |
Always show debug output, even on passing tests |
--debug-strip-tmp-path |
Strip tmp_path prefix from Path objects (default) |
--no-debug-strip-tmp-path |
Show full absolute paths |
--debug-list-dir-contents |
Show directory tree for Path directories |
--no-debug-list-dir-contents |
Don't list directory contents (default) |
--debug-max-depth=N |
Limit nesting depth in pretty-printed output |
--debug-max-length=N |
Limit collection length in pretty-printed output |
--debug-show-type |
Show type annotations above each value |
--no-debug-show-type |
Don't show type annotations (default) |
INI Options
Add these to pyproject.toml under [tool.pytest.ini_options]:
[tool.pytest.ini_options]
print_debug = true
debug_strip_tmp_path = true
debug_list_dir_contents = false
debug_max_depth = 4
debug_max_length = 20
debug_show_type = false
Option Precedence
Per-call arguments win, then CLI flags, then INI settings:
per-call override > CLI flag > INI option > built-in default
Stripped capsys Output
The plugin overrides the built-in capsys fixture so that readouterr() returns post-processed strings. Two transformations are available:
- ANSI escape stripping (default: on)
tmp_pathprefix stripping (default: off, opt-in)
Both can be disabled or enabled independently, and they compose when both are active.
ANSI Escape Stripping
Tests that exercise code printing colored output (Rich, Click, Colorama, etc.) usually don't care about the escape codes. By default, they're removed before you see the captured string:
def test_greeting(capsys):
print("\x1b[32mHello, world!\x1b[0m")
captured = capsys.readouterr()
assert captured.out == "Hello, world!\n"
To keep the codes for a single test, mark it with @pytest.mark.keep_ansi:
import pytest
@pytest.mark.keep_ansi
def test_color_codes(capsys):
print("\x1b[32mgreen\x1b[0m")
captured = capsys.readouterr()
assert "\x1b[32m" in captured.out
To turn stripping off globally:
pytest --no-strip-ansi
tmp_path Stripping
Code that prints a tmp_path-rooted file produces output like /var/folders/.../pytest-1234/test_foo0/file.txt, which is awkward to assert on. Opt in to capsys tmp_path stripping to collapse those prefixes to their relative portion:
def test_writes_log(capsys, tmp_path):
log = tmp_path / "app.log"
print(f"wrote {log}")
captured = capsys.readouterr()
assert captured.out == "wrote app.log\n"
Enable it for one run:
pytest --capsys-strip-tmp-path
Or globally in pyproject.toml:
[tool.pytest.ini_options]
capsys_strip_tmp_path = true
--no-capsys-strip-tmp-path overrides the INI setting for a single run. Stripping applies to both captured.out and captured.err.
[!NOTE] When both transformations are active, ANSI codes are stripped first, then
tmp_pathprefixes. The order matters only if your output mixes the two, but the combined result is what you'd expect.
INI Options
[tool.pytest.ini_options]
strip_ansi = true # default: true
capsys_strip_tmp_path = false # default: false
Visible Whitespace in Assertions
When two strings differ only in whitespace, pytest's default diff is hard to read. This plugin replaces invisible characters with visible Unicode symbols in the assertion failure message.
Symbol Reference
| Character | Symbol | Name |
|---|---|---|
| Trailing space | · |
Middle dot |
Tab (\t) |
→ |
Rightwards arrow |
Carriage return (\r) |
← |
Leftwards arrow |
Newline (\n) |
↵ |
Return symbol |
Example Output
For a test like:
def test_output():
assert "hello " == "hello"
The failure message shows:
AssertionError: 'hello·' == 'hello'
Whitespace-visible comparison:
Left: 'hello·'
Right: 'hello'
Disabling
Use the --no-show-whitespace CLI flag, or set the INI option:
[tool.pytest.ini_options]
show_whitespace = false
[!NOTE] Whitespace visibility activates only for
==comparisons between strings, and only when the replacement actually changes how the string displays. Non-string comparisons and strings without notable whitespace are unaffected.
Terminal Column Width
Many terminal-aware libraries (Rich, Click, etc.) detect terminal width at runtime. In test environments, the detected width is often very small, which causes unwanted line wraps in captured output. This plugin can set the COLUMNS environment variable for every test to keep output stable.
The feature is disabled by default. Enable it with the --columns CLI flag or via INI options.
CLI Option
Set COLUMNS for a single run:
pytest --columns=180
INI Options
Enable it permanently in pyproject.toml:
[tool.pytest.ini_options]
set_columns = true # turn the feature on
columns = 180 # value to set when enabled
The --columns CLI flag overrides the INI columns value when both are present.
Configuration Summary
Every feature is configurable through CLI flags and pyproject.toml INI options. The debug fixture additionally supports per-call arguments.
| Feature | Default | Toggle with |
|---|---|---|
| Debug fixture | Output on failure | Always available; --print-debug to also show on success |
| ANSI stripping | On | --no-strip-ansi or strip_ansi = false |
tmp_path in capsys |
Off | --capsys-strip-tmp-path or capsys_strip_tmp_path = true |
| Visible whitespace | On | --no-show-whitespace or show_whitespace = false |
| Column width | Off | --columns=N or set_columns = true |
AI Policy
All AI generated content is and always will be meticulously reviewed and approved by the author.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_devtools-1.2.0.tar.gz.
File metadata
- Download URL: pytest_devtools-1.2.0.tar.gz
- Upload date:
- Size: 12.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
543dffe00d459211dbe06b249d3f8db6ee99ffee392ec38ef0b0b1774a7deddf
|
|
| MD5 |
c5170b80cfd9ca5aaedfc490f9926f45
|
|
| BLAKE2b-256 |
cbcca42abca748669bd3c93bf59d9761313aa6c16c46876950d944068f65e174
|
File details
Details for the file pytest_devtools-1.2.0-py3-none-any.whl.
File metadata
- Download URL: pytest_devtools-1.2.0-py3-none-any.whl
- Upload date:
- Size: 14.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82b980eebf446d90e8c272d1908ddc934720a6bc6086060d00ab0e26730c1f3f
|
|
| MD5 |
7af7143abd80575ad9955f7448de3888
|
|
| BLAKE2b-256 |
92edfd42bf209f430f47e069c32c13fdba9c61447930f1a469199ea19ef5e3df
|