Skip to main content

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:

  1. Testing — exercise your CLI tool's entry-points from pytest and assert on their stdout.
  2. 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

  1. Install coverage: pip install clitag[coverage]

  2. A .coveragerc (or [tool.coverage] in pyproject.toml) in your project root with:

    [run]
    parallel = true
    branch = true
    
  3. Run coverage combine after 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 interactive sendline input.
  • 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)

Source distribution for clitag 1.0.0
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)

Table of built distributions (wheels) for clitag 1.0.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page