Documentation#

Imagine for a second if somebody handed you the NumPy package with no documentation 😢. Pretty daunting, eh? Bottomline: you should do a favor to your users (and to the future version of yourself, six months from now) and ship good documentation with your package. When it comes to tooling, nowadays this is simply a no brainer: all Python projects out there use Sphinx to generate the documentation, Python itself uses Sphinx, and so should you!

When you think about documenting a software project, there are actually to equally important parts to it, namely:

  • descriptive text explaining the purpose and usage of the project (this might include, among other things, a user guide, examples, tutorials and so on and so forth);

  • technical documentation (sometimes referred to as Advanced Programming Interfaces, or APIs), describing, e.g., the inner workings of the classes and functions that your package makes available (this might include, among other things, function signatures and return values).

Just to put things in context, the reference page for the optimize module in SciPy (or this very page that you are reading right now, for what it’s worth) is a good example of the first, while the documentation of the curve_fit function belongs to the second category.

Sphinx handles both things equally well—the first is expressed in the form of a series of .rst markup files, while the second is extracted from the docstrings in the Python code. What you need is a Python configuration file such as the one generating this page

# Configuration file for the Sphinx documentation builder.
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

import importlib.metadata

from metarep import __version__, __name__ as __package_name__


# Get package metadata.
_metadata = importlib.metadata.metadata(__package_name__)


# --- Project information ---
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = __package_name__
author = _metadata["Author-email"]
copyright = f"2025-%Y, {author}"
version = __version__
release = version


# --- General configuration ---
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.todo",
    "sphinx.ext.viewcode",
]
autodoc_default_options = {
    "members": True,
    "member-order": "bysource",
    "undoc-members": True,
    "private-members": True
}
todo_include_todos = True

# Options for syntax highlighting.
pygments_style = "default"
pygments_dark_style = "default"

# Options for internationalization.
language = "en"

# Options for markup.
rst_prolog = f"""
.. |Python| replace:: `Python <https://www.python.org/>`__
.. |Sphinx| replace:: `Sphinx <https://www.sphinx-doc.org/en/master/>`__
.. |numpy| replace:: `NumPy <https://numpy.org/>`__
.. |GitHub| replace:: `GitHub <https://github.com/>`__
"""

# Options for source files.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# Options for templating.
templates_path = ["_templates"]


# --- Options for HTML output ---
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "sphinxawesome_theme"
html_theme_options = {
    "awesome_external_links": True,
}
html_logo = "_static/logo_small.png"
html_favicon = "_static/favicon.ico"
html_permalinks_icon = "<span>#</span>"
html_static_path = ["_static"]

and at least a master markup file index.rst

.. metarep documentation master file, created by
   sphinx-quickstart on Mon Sep  1 14:14:13 2025.
   You can adapt this file completely to your liking, but it should at least
   contain the root `toctree` directive.


metarep documentation
=====================


.. image:: _static/logo.png
   :alt: Project logo
   :width: 200px
   :align: left


This is an unusual repo. From the pure code standpoint it provides a single |Python| module
exposing exactly one function. (The latter calculates the square of a given number or
numpy array, in case you do care.)

In the process, though, we touch upon many different points related to the development and
maintainance of a simple Python package, and the underlying repository can be taken as a
rough template and a starting point for new projects.


.. toctree::
   :maxdepth: 1
   :caption: Contents:

   setup
   layout
   install
   versioning
   documentation
   api
   linting
   testing
   tasks
   actions
   license
   badges
   releasing
   release_notes

Note

Wow, did you see that? We just included an entire file from the repository verbatim into the documentation. This is what the literalinclude directive is for.

The most straightforward way to begin a new project is to use the quickstart facility provided by Sphinx itself, which will generate a basic set of files

cd docs
sphinx-quickstart

and then proceed from there.

Compiling the documentation#

All right, now I have a fully fledged docs folder with all the ingredients for great documentation—what should I do with it?

Well, for one thing, if you did use sphinx-quickstart you will find in the docs folder a fully fledged Makefile (on Linux/Mac) or a make.bat (on Windows) and the magic is simply

cd docs
make html

or

cd docs
make.bat html

depending on your platform. This will trigger the Sphinx build process and generate the HTML documentation in the _build/html directory.

This is equivalent to running the following command

cd docs
sphinx-build -b html docs _build/html

and, in fact, you might want to wrap just that into a session of your noxfile.py if you happen (as you should) to use nox, as described in the section about Common tasks.

See also

Common tasks.

Markup files and docstrings#

As noted before, there are two main parts to software documentation: general, descriptive text and technical descriptions of the interfaces.

For the first, you just create a bunch of .rst files in markdown format, include them into the index.rst file, and you’re good to go. Start from here if you want to become proficient in reStructuredText (reST), the default plaintext markup language used by Sphinx.

The interfaces, one the other hand, are customarily described in the code source files in the form of doctrings. Having code and documentation living side by side in the same file makes it easier for the developer to update the second when the first is changed! In the context of Sphinx, docstrings get automatically parsed and formatted via the autodoc directive.

Note

autodoc is part of the Sphinx, but it comes in the form of an extensions, and you do have to explicitly opt-in to use it. This typically entails to adding (at least) a line to your conf.py file

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

Let’s see how this plays out in practice. Take the only Python module we are actually shipping with this repository

# Copyright (C) 2025 Luca Baldini (luca.baldini@pi.infn.it)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


def square(x):
    """Return the square of a single number or a numpy array.

    Note by virtue of the ``2.`` (vs. ``2``) at the exponent, the output value
    is always a float, or an array of floats, even if the input is an integer
    (or an array of integers).

    Example
    -------

    >>> square(2)
    4.0
    >>> square(np.array([1, 2, 3]))
    array([1., 4., 9.])

    Arguments
    ---------
    x : array_like
        Input value(s) to be squared. This can be either a number (int or float)
        or a numpy array.

    Returns
    -------
    array_like
        The squared value(s) of the input.
    """
    return x**2.

along with the markup file

.. _api:

APIs
====

.. automodule:: metarep.utils

And look at the resulting API documentation in the APIs section. Cool, isn’t it?

Sphinx themes#

With no offense to anybody, the default theme that Sphinx uses to generate HTML documentation is not particularly inspiring. The website sphinx-themes.org provides an extensive collection of themes for Sphinx documentation. I have no doubt that there are useful places out there, but I recommend starting from there.

Warning

Using a custom theme is done by setting the html_theme variable in your Sphinx configuration file (conf.py), e.g.,

html_theme = 'sphinxawesome_theme'

and making sure that you have the theme installed in your environment (usually via pip). The most robust way to achieve the latter is to list the proper Python package among the optional dependencies in your pyproject.toml file

  [project.optional-dependencies]
  docs = [
     "sphinx",
     "sphinxawesome-theme",
    ]


If you change theme, you will have to tweak both files!

Publishing the documentation#

At this point you might be wondering: all right I have all this great stuff on my laptop, and I managed to compile it, but where do I put the static html output so that people can actually see it? Very good question.

This is one area where GitHub Actions come into play. To make a long story short, go ahead and drop into the .github/workflows a file containing something along the lines of

name: Docs

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  pages:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    permissions:
      pages: write
      id-token: write
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: "3.13"
    - run: python -m pip install --upgrade pip
    - run: pip install -e ".[docs]"

    - id: deployment
      uses: sphinx-notes/pages@v3

and you will get your glorious documentation recompiled and deployed on GitHub Pages every time you push changes to the main branch, be that via a pull request or directly (which, remember, you should not).

Warning

In order for this to work as advertised you will have to enable GitHub Pages for your repository. That is: go to your repository “Settings” -> “Pages” and set the “Source” combo box to “GitHub Actions”. That should do it.

See also

GitHub actions.