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