12  Software Documentation

Further reading

12.1 Code comments

Code comments are annotations you write directly in the source code and:

  • are written for users (developers!) who deal with your source code
  • explain parts that are not intuitive from the code itself
  • explain the purpose of a piece of code (why over how)
  • need to be kept up-to-date as wrong comments are not caught through testing
  • do not replace readable and structured code
  • do not turn old code into commented zombie code (see code smells)
  • do not repeat in natural language what is written in your code, e.g.
% Now we check if the age of a patient is greater than 18
if agePatient > 18

12.2 Docstrings

Docstrings are structured comments, associated with segments (rather than lines) of code which can be used to generate documentation for users (YOU!) of your project. They allow you to provide documentation to a function/class/method, that is relevant for the user.

Two docstring styles are commonly used for their readability:

def func(arg1, arg2):
    """Summary line.

    Extended description of function.

    Args:
        arg1 (int): Description of arg1
        arg2 (str): Description of arg2

    Returns:
        bool: Description of return value

    """
    return True

⮕ Check out the Google style guide or a full example.

def func(arg1, arg2):
    """Summary line.

    Extended description of function.

    Parameters
    ----------
    arg1 : int
        Description of arg1
    arg2 : str
        Description of arg2

    Returns
    -------
    bool
        Description of return value

    """
    return True

⮕ Check out the NumPy style guide or a full example.

12.2.1 Docstring formatting

Python’s PEP 257 provides guidelines on how to effectively write docstrings to ensure they are clear, concise, and useful. Some pointers:

  • The summary sentence of the docstring should appear on the same line as the opening triple quotes.
  • The closing triple quotes should be placed on a separate line, except for one-line docstrings.
  • Docstrings for methods and functions should not have blank lines before or after them.
def find_max(numbers):
    """Find the maximum value in a list of numbers.

    Parameters
    ----------
    numbers : iterable
        A collection of numerical values from which the maximum will be determined.

    Returns
    -------
    max_value : `float`
        The highest number in the given list of numbers.
    """
    pass
  • Docstrings for classes should immediately follow the class definition without any preceding blank lines. However, a single blank line should follow the docstring, separating it from subsequent code such as class variables or the init method.
class Circle(object):
    """A circle defined by its radius.

    Parameters
    ----------
    radius : `float`
        The radius of the circle.
    """

    def __init__(self, radius):
        self.radius = radius
  • The content of a docstring must align with the indentation level of the code it documents.

👍

def get_length(items):
    """Calculate the number of items in the list.

    Parameters
    ----------
    items : list
        A list whose length is to be determined.

    Returns
    -------
    length : int
        The number of items in the list.
    """
    return len(items)

👎

def get_length(items):
    """Calculate the number of items in the list.

Parameters
----------
items : list
    A list whose length is to be determined.

Returns
-------
length : int
    The number of items in the list.
"""
    return len(items)
Further reading

12.2.2 Docstring contents

Formatting conventions are important for clarity and readability across different APIs or libraries. As mentioned we adhere to the numpydoc convention.

Summaries

Docstrings should start with a one-sentence summary and if additional clarification is needed, you could add an extended summary. For functions and methods, use imperative voice, framing its summary as a command or instruction that the user can execute through the API. For classes, the summary should clearly describe what the class represents or its primary responsibility.

Parameters and arguments

The Parameters section lists the input parameters of a class, function, or method. It should include the parameter name, type, and a brief description of what the parameter represents. Parameters are listed in the same order as they appear in the function definition.

Describing parameters

Basic example:

def calcDistance(x, y, x0=0., y0=0., **kwargs):
    """Calculate the distance between two points.

    Parameters
    ----------
    x : `float`
        X-axis coordinate.
    y : `float`
        Y-axis coordinate.
    x0 : `float`, optional
        X-axis coordinate for the second point (the origin,
        by default).

        Descriptions can have multiple paragraphs, and lists:

        - First list item.
        - Second list item.
    y0 : `float`, optional
        Y-axis coordinate for the second point (the origin,
        by default).
    **kwargs
        Additional keyword arguments passed to
        `calcExternalApi`.
    """

Returns and Yields

Returns is an explanation about the returned values and their types, following the same format as Parameters. This is applicable to functions and methods. Use Yields for generators.

Basic example for returns:

def getCoord(self):
    """Get the point's pixel coordinate.

    Returns
    -------
    x : `int`
        X-axis pixel coordinate.
    y : `int`
        Y-axis pixel coordinate.
    """
    return self._x, self._y

Basic example for yields:

def items(self):
    """Iterate over items in the container.

    Yields
    ------
    key : `str`
        An item's key.
    value : obj
        An item's value.
    """
    for key, value in self._data.items():
        yield key, value

Raises

For classes, methods, and functions the Raises section is used to describe exceptions that are explicitly raised.

Raises
------
IOError
    Raised if the input file cannot be read.
TypeError
    Raised if parameter ``example`` is an invalid type.

12.3 Tooling

There are various tools available that can help you enhance the creation, management, and deployment of your project documentation.

12.3.1 Sphinx

Sphinx is a versatile documentation tool that is well-suited for documenting Python projects due to its easy integration with Python’s docstrings. Its capabilities extend beyond Python, making it a great solution for creating comprehensive documentation for projects in various programming languages, such as MATLAB.

Some key features of Sphinx include:

  • Cross-referencing code and documentation across files.
  • Automatic generation of documentation from docstrings.
  • Syntax highlighting for code examples.
  • Support for extensions and custom themes.
  • Multiple output formats.

Getting started with Sphinx

Tip

To get started with sphinx, we recommend the Coderefinery lesson on Spinx and Markdown

  1. Install dependency: You can install Sphinx in various ways, either through apt-get for Linux, Homebrew for macOS, or through Chocolatey for Windows. Assuming you have Python on your machine you can install it through conda or pip.
  2. Setup documentation: Create a directory for your documentation (/docs), and run sphinx-quickstart in that directory. The default answers to the questions are fine.
  3. Configure sphinx: Once you have the conf.py and index.rst files, you will need to modify them further. The index.rst file acts as the front page of your documentation and the root of the table of contents. The conf.py file is the main configuration file for the Sphinx documentation. It holds all your extensions and controls various aspects of the build process that can be customized to suit your needs. For example, sphinx.ext.autodoc is used for pulling documentation from docstrings, and sphinx.ext.mathjax for displaying mathematical content.
  4. Write content:”Add content to your documentation. In addition to reStructureText, sphinx also integrates with Markdown documentation through the MyST parser.
  5. Build documentation: Once you have added the documentation files, you can build the documentation from the folder /docs with sphinx-build . _build/ or make html.
  6. Further customization: You can customize the look of your documentation by changing themes in the conf.py file.
Templates
# Configuration file for the Sphinx documentation builder
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, as shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))


# -- Project information -----------------------------------------------------

project = "Project"
copyright = "year, name"
author = "name"

# The master toctree document.
master_doc = "index"

# The full version, including alpha/beta/rc tags
release = "0.1.0"

# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "myst_parser",
    "sphinxcontrib.matlab", # Required for MATLAB
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx_copybutton",
    "sphinx.ext.viewcode",
    "sphinx_tabs.tabs"
]

myst_enable_extensions = [
    "linkify",
]

# MATLAB settings for autodoc
# here = os.path.dirname(os.path.abspath(__file__))
# matlab_src_dir = os.path.abspath(os.path.join(here, ".."))
# primary_domain = "mat"

# Napoleon settings
napoleon_google_docstring = True
# napoleon_numpy_docstring = True
# napoleon_use_param = False
# napoleon_preprocess_types = True

# This value contains a list of modules to be mocked up.
# autodoc_mock_imports = []

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.

# html_theme = "sphinx_rtd_theme"
html_theme = "sphinx_book_theme"
# html_theme = "pydata_sphinx_theme"


html_title = "title"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Sphinx extensions
sphinx
sphinxcontrib-matlabdomain # Only for matlab source code
sphinx-tabs
sphinx-copybutton

# MyST parser
myst-parser
linkify-it-py

# Themes
pydata-sphinx-theme
sphinx-book-theme
sphinx-rtd-theme
Example repositories using Sphinx for Python:

Sphinx-matlabdomain

For documenting MATLAB projects, Sphinx can be extended for MATLAB. The sphinxcontrib-matlabdomain extension allows Sphinx to interpret and render MATLAB specific documentation. The extension can be installed through pip install sphinxcontrib-matlabdomain and added the extension to the conf.py file.

Example repositories using sphinx for MATLAB:

Sphinx autodoc

Once the sphinx config.py is set up, you can generate the API reference documentation by using the sphinx-autodoc extension. By creating .rst files with the autodoc syntax, sphinx will build the API reference.

Example of creating docstrings for all scripts in a module (folder):

Flightpaths module
==================

.. automodule:: src.common.flightpaths
    :members:
    :undoc-members:
    :show-inheritance:

12.3.2 Quarto

Quarto allows to create dynamic documents, presentations, reports, websites, and more, using multiple programming languages, including Python, R, and Julia. It enables the inclusion of interactive visualizations, equations, and other multimedia elements directly in the documents.

Success

⮕ The AWE Developer guides are created with Quarto!

Getting started

  1. Downloading: You can download the installer for your operating system from the Quarto website.
  2. Running Quarto: You can run Quarto either from your command line or from VS Code, JupyterLab, RStudio, or any text editor. For VS Code you will need to install a Quarto extension. It is a stand-alone application and does not require Python.
  3. Markdown flavour: Quarto projects use .qmd files which are a Markdown flavour.
    ---
    title: "Your Document Title"
    format: html # Or pdf, word, etc.
    ---

    # Introduction

    Some text...

    ## Section 1

    Some text...

    ```python
    # This is a code block
    import pandas as pd
    data = pd.read_csv("data.csv")
    print(data.head())
    ```

    ## Section 2

    Some more text....
  1. Adding content: Write your text using standard Markdown syntax and add code blocks.

  2. Building documentation

    • You can watch a file or directory for changes and automatically re-render with quarto preview your-file-name.qmd, which is useful to see live updates.
    • To compile a Quarto document, use quarto render your-file-name.qmd. This command converts your .qmd file into the output format specified in the file’s header (e.g., HTML, PDF).
  3. Additional features:

    • Quarto supports cross-referencing figures, tables, and other elements within your document. You can also use BibTeX for citations.
    • You can have interactive components for web outputs (e.g. embeded Plotly charts).
    • Extensive options for custom styles and layouts.
  4. Publishing: Quarto documents are portable and can be shared as is, allowing others to compile them on their own systems or published by hosting the output files on a server like GitHub Pages.

Examples: Quarto gallery

Note

In order to create PDFs you will need to install a LaTeX engine if you do not have one installed already. You could use a lightweight distribution like TinyTeX, which you can install with quarto install tool tinytex.

12.3.3 Jupyter Book

Jupyter Book uses Sphinx to convert notebooks and Markdown documents into interactive and navigable websites, similar to Quarto.

  • Jupyter Book primarily focuses on integrating Jupyter notebooks with Sphinx’s documentation capabilities, enabling features like cell execution and output caching directly within the documentation.
  • Quarto offers broader language support and emphasizes reproducibility across these environments.
Note

The TU Delft OPEN Interactive Textbooks platform uses JupyterBook to create textbooks.

Features:

  • Jupyter Book can integrate outputs by allowing code execution within the content, making it ideal for tutorials, courses, and technical documentation that require live examples.
  • Jupyter Book uses Markdown for Jupyter (MyST) which extends the traditional Markdown syntax to include features normally available in reStructuredText (reST). This makes it easier to include complex formatting and dynamic content directly in Markdown files.
  • Jupyter Book can execute notebook cells and cache outputs. This means that content including code outputs can be generated once and reused.

Getting started:

JupyterBook has extensive documentation on getting started with building a book.

Tip

Essentially, both Jupyter Books and Quarto offer similar features, but a significant advantage of Jupyter Books is its support for MATLAB integration, which Quarto does not currently offer.

12.4 Hosting

Once you have created your documentation either in Sphinx, Quarto, or Jupyter Book, you can host it online. There are several platforms available that can help you deploy and manage your documentation.

12.4.1 GitHub Pages

GitHub Pages provides a simple way to host your documentation, especially if your project is already on GitHub.

It is straightforward to set up GitHub Pages:

  1. Within your repository, go to the repository settings and find the GitHub Pages section.
  2. Choose your publishing source (you should have a docs folder or a dedicated branch).

It also supports custom domains, which might be relevant to the AWE group, and you can configure this by adding a CNAME file to your directory.

12.4.2 MkDocs

MkDocs is a hosting platform that uses Markdown for all documentation, simplifying the writing process, and is configured with a single YAML file.

Getting started:

  1. You can install it through pip (pip install mkdocs). Then you can initialize your MkDocs project by running mkdocs new your_project_name.
  2. Place your Markdown documentation in your docs directory and define the structure in your mkdocs.yml file.
  3. You can preview your site locally and see live updates as you make changes by running mkdocs serve.
  4. When you want to publish your documentation run mkdocs build.
  5. MkDocs is designed to be hosted on almost any static file server and works well with GitHub Pages.
  • MkDocs official site that includes a Getting Started and User Guide.

12.4.3 Read the Docs

Read the Docs is a platform that simplifies the hosting of documentation. It integrates particularly well with Sphinx, allowing for the automatic building and hosting of your project’s documentation. Read the Docs supports automatic builds and version control, enabling users to switch between different versions of the documentation to match the version of the software they are using. Additionally, it offers support for custom domains. It offers a free service for open-source projects, which includes features like version control and automatic builds. However, for private or commercial projects, Read the Docs requires a paid subscription.

The set-up is also extremely simple:

  1. Sign up and import your documentation repository.
  2. Connect to your GitHub account.
  3. Configure your project settings within their dashboard