clitag
A CLI tester and documentation generator.
clitag runs CLI commands as subprocesses, collects their output, and renders
the results as Markdown documentation. It is designed to serve two purposes at
once:
- Testing — exercise your CLI tool's entry-points from pytest and assert on their stdout.
- Documentation generation — because the steps are run in a real test, the resulting Markdown is always up-to-date. Write it to a file from your test and commit it alongside your code.
Installation
pip install clitag
If you want subprocess coverage tracking (see below):
pip install clitag[coverage]
For development / running the test suite:
pip install clitag[testing]
Basic usage
from clitag import CliTester
ct = CliTester()
ct.title = "My CLI example"
ct.description = "The following steps show typical usage."
output = ct.run_step(
cmd="echo",
args=["hello, world"],
description="Print a greeting",
)
assert "hello" in output
ct.run_step(
cmd="my-tool-status",
description="Check the status",
)
print(ct.format_md())
run_step returns the decoded stdout of the process, so you can assert on it
directly alongside capturing the documentation.
Writing the Markdown to a file
A common pattern is to write the Markdown from within a pytest test so it is regenerated on every run:
def test_cli_example(tmp_path):
ct = CliTester()
ct.title = "Example usage"
ct.run_step("my-tool-cmd", description="Run the command")
with open("docs/example_usage.md", "w") as f:
f.write(ct.format_md())
Coverage convention
Why subprocess coverage is tricky
pytest-cov / coverage.py only tracks the current Python process. When
your CLI entry-points are invoked as subprocesses (e.g. my-tool-cmd), coverage
is lost for those code paths.
How clitag solves it
When a CLI_PREFIX is configured, clitag rewrites matching commands to:
coverage run --data-file=... --rcfile=... -m <module> <args>
For example, with CLI_PREFIX = "my_tool_" and
CLI_PREFIX_REPLACE = "my_tool.cli_", the command my-tool-cmd becomes:
coverage run --data-file=.coverage --rcfile=.coveragerc -m my_tool.cli_cmd
clitag also sets the COVERAGE_PROCESS_START environment variable so that
child processes started by the CLI command also report coverage.
Requirements for coverage to work
-
Install
coverage:pip install clitag[coverage] -
A
.coveragerc(or[tool.coverage]inpyproject.toml) in your project root with:[run] parallel = true branch = true
-
Run
coverage combineafter the test suite to merge the parallel data files.
Usage patterns
Constructor — ad-hoc:
ct = CliTester(
cli_prefix="my_tool_",
cli_prefix_replace="my_tool.cli_",
)
Subclass — recommended for whole-project reuse:
class MyProjectTester(CliTester):
CLI_PREFIX = "my_tool_"
CLI_PREFIX_REPLACE = "my_tool.cli_"
Subclassing is preferred when you have many test files that all test the same project, because the prefix convention is defined once and inherited everywhere.
When CLI_PREFIX is None (the default), no coverage wrapping is applied
and commands are executed as-is.
pytest fixture
Installing clitag registers a cli_tester pytest fixture automatically via
the pytest11 entry-point — no import or conftest declaration is needed:
def test_something(cli_tester):
output = cli_tester.run_step("echo", args=["hello"])
assert "hello" in output
The fixture returns a plain CliTester() with no prefix configured. To use
a project-specific subclass, override the fixture in your conftest.py:
# conftest.py
import pytest
from clitag import CliTester
class MyTester(CliTester):
CLI_PREFIX = "my_tool_"
CLI_PREFIX_REPLACE = "my_tool.cli_"
@pytest.fixture()
def cli_tester():
return MyTester()
Cross-platform notes
clitag handles the platform difference internally:
- Linux / macOS — uses
pexpect.spawn, which supports a full PTY and interactivesendlineinput. - Windows — uses
pexpect.popen_spawn.PopenSpawn, which does not require a PTY but otherwise behaves the same.
License
MIT — see LICENSE.
Metadata
Release files for clitag 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| clitag-1.0.0.tar.gz | 16.1 kB | With provenance | Details for clitag-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| clitag-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for clitag-1.0.0-py3-none-any.whl |
Total release size: 23.2 kB
Release files / clitag-1.0.0.tar.gz
| Download URL | clitag-1.0.0.tar.gz |
|---|---|
| Size | 16.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0ce42cb852476e297895a34467b24cb4ad6425ced8955f048fb557066a7a74f1
|
|
BLAKE2b-256 checksum How to use checksums |
d846b2742a074081adc9ad53dd9ccd0ff208d6a55d77b1ae39e6a51979af05c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 5, 2026.
Transparency logRelease files / clitag-1.0.0-py3-none-any.whl
| Download URL | clitag-1.0.0-py3-none-any.whl |
|---|---|
| Size | 7.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
54e430c9e7755bb6da7bb2466c0879190c9728585dca0a28259d74d71d1945ab
|
|
BLAKE2b-256 checksum How to use checksums |
4ab8cc40a38abd2425e572e48b368736f79d112482f00a0cb305a398a25e6421
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 5, 2026.
Transparency logSecurity for clitag 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for clitag-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.