The Coding Lab
<- All posts

Documentation as Part of the Product - 7/9

Designing documentation for users and maintainers, then building tested API and project documentation with Sphinx.

A command can be correct, packaged, and tested while still being difficult to use. Documentation closes that gap by explaining what the tool is for, how to start, how to complete common tasks, and what its public interfaces mean.

Good documentation is not a single long README. Different readers arrive with different questions, so the first design decision is deciding which question each document should answer.

Series

  1. From a Script to a Command-Line Tool
  2. When sys.argv Stops Scaling
  3. Designing a CLI with argparse
  4. Why a Bash Installer Becomes a Liability
  5. Packaging a Python CLI with Console Scripts
  6. Testing a Python CLI and Automating CI
  7. Documentation as Part of the Product
  8. Building a Reliable PyPI Release Pipeline
  9. Publishing a Pure-Python CLI with Conda

Start with reader intent

DocumentQuestion it answers
READMEWhat is this project, and should I use it?
TutorialHow do I learn the basic workflow?
How-to guideHow do I complete one specific task?
ReferenceWhat exactly does this option, function, or format mean?
ExplanationWhy does the system work this way?

Not every small project needs all five as separate documents. The distinction is still useful: a README becomes hard to scan when installation, API reference, design rationale, and troubleshooting all compete at the same level.

Code should expose intent

Readable names, focused functions, type hints, and small interfaces reduce how much separate explanation is necessary. They do not eliminate documentation. A docstring should describe the contract or important behavior that the signature cannot express:

def http_url(value: str) -> str:
    """Return an absolute HTTP(S) URL accepted by the CLI.

    Values without a scheme default to HTTPS.

    Args:
        value: URL supplied by the caller.

    Raises:
        argparse.ArgumentTypeError: If the URL has no host or uses an
            unsupported scheme.
    """

A comment should explain a non-obvious reason or constraint, not translate the next line into English. Git history can provide useful archaeology, but commit messages are not a replacement for documenting a public contract or a decision that future maintainers need to understand.

Tools such as GitLens make that history visible next to the code, which is especially useful when investigating why a line changed:

GitLens showing commit context beside Python code in VS Code

History supplies context; the current code and documentation must still describe the current contract.

Make the README earn the first minute

For tinyHTTPie, the README should answer the first questions with little scrolling:

A polished project README with a logo, concise description, links, badges, and a product screenshot

The Lounge README uses visual hierarchy to communicate identity, purpose, project links, status, and the product itself.

# tinyHTTPie

A small command-line HTTP client used to demonstrate the lifecycle of a
Python CLI.

## Install

    python -m pip install tihttp

## Use

    tihttp --headers-only https://example.com
    tihttp --body-only https://example.com

## Develop

    python -m pip install --editable '.[dev]'
    python -m pytest

Badges can report automation status or package versions. They do not prove project quality, so each badge should answer a real reader question rather than decorate the page.

Build larger documentation with Sphinx

Sphinx becomes useful when tutorials, reference material, and generated API documentation outgrow the README. Install the project and documentation dependencies into the active environment, then create the documentation structure:

$ python -m pip install --editable '.[docs]'
$ mkdir docs
$ cd docs
$ sphinx-quickstart

> Separate source and build directories (y/n) [n]: y
> Project name: tinyHTTPie
> Author name(s): Niklas Tiede
> Project release []: 0.1.0

The generated Makefile lives in docs, so that is where make html runs:

$ make html

The central configuration is docs/source/conf.py:

project = "tinyHTTPie"
release = "0.1.0"

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
]

html_theme = "sphinx_rtd_theme"

autodoc imports documented modules, which is why installing the project is cleaner than modifying sys.path inside the documentation configuration. napoleon lets Sphinx understand common Google- and NumPy-style docstrings.

The root document can then organize material by reader task:

tinyHTTPie
==========

.. toctree::
   :maxdepth: 2

   tutorial
   how-to
   reference
   design

Treat documentation warnings as failures

Broken cross-references and invalid directives are defects. CI can build the documentation in strict mode:

$ python -m sphinx -W --keep-going \
    -b html docs/source docs/build/html

Documentation also becomes stale when commands and examples stop matching the software. Testing important examples and building docs on every change keeps that drift visible.

With the user and maintainer interfaces documented, the project is ready for a repeatable release. Part 8 packages and publishes the same artifacts that we have tested.

Further reading

<< section 6 | section 8 >>