Sphinx API documentation#

Sphinx is a powerful documentation tool, based on reStructuredText.

If you are more comfortable with markdown, you can use MyST

ls
env-1                   pipfile       pip-tools         sphinx-docs.ipynb
environments.ipynb      Pipfile       requirements.txt
from-script-to-project  Pipfile.lock  sphinx-docs
cd sphinx-docs
ls
docs  docs-2023  docs-done  package_to_document  __pycache__

We can initialize our docs with the command:

sphinx-quickstart

which will prompt with a few questions to set up the docs directory.

There are two key files created (and several more):

  1. conf.py - the sphinx configuration file

  2. index.rst the first page of our documentation. This is the landing page for our docs

Let’s start by checking out the docs:

make html

in the docs directory, then open _build/index.html

The first thing we are going to do is add the autodoc extension. autodoc is an extension that defines directives (commands) for automatically documenting your code.

We add this extension to the conf.py file

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

Our module myarray defines an Array class and an asarray function. We want to docment these. Since we went through the trouble of adding docstrings, it would be nice to import those into our docs! That’s what autodoc is for.

Inspecting our module and class, we can see their docstrings:

conda activate UIO-IN3110
python3 -c "import myarray; help(myarray)"
(UIO-IN3110) 
Help on package myarray:

NAME
    myarray

PACKAGE CONTENTS
    myarray

CLASSES
    builtins.object
        myarray.myarray.Array
    
    class Array(builtins.object)
     |  Array(shape, *values)
     |  
     |  A class defining an API for arrays
     |  
     |  Methods defined here:
     |  
     |  __add__(self, other)
     |      Element-wise adds Array with another Array or number.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to add element-wise to this array.
     |      
     |      Returns:
     |          Array: the sum as a new array.
     |  
     |  __eq__(self, other)
     |      Compares an Array with another Array.
     |      
     |      If the two array shapes do not match, it should return False.
     |      If `other` is an unexpected type, return False.
     |      
     |      Args:
     |          other (Array): The array to compare with this array.
     |      
     |      Returns:
     |          bool: True if the two arrays are equal. False otherwise.
     |  
     |  __init__(self, shape, *values)
     |      Make sure that you check that your array actually is an array, which means it is homogeneous (one data type).
     |      
     |      Args:
     |          shape (tuple): shape of the array as a tuple. A 1D array with n elements will have shape = (n,).
     |          values: The values in the array. These should all be the same data type. Either numeric or boolean.
     |      
     |      Raises:
     |          ValueError: If the values are not all of the same type.
     |          ValueError: If the number of values does not fit with the shape.
     |  
     |  __mul__(self, other)
     |      Element-wise multiplies this Array with a number or array.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to multiply element-wise to this array.
     |      
     |      Returns:
     |          Array: a new array with every element multiplied with `other`.
     |  
     |  __radd__(self, other)
     |      Element-wise adds Array with another Array or number.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to add element-wise to this array.
     |      
     |      Returns:
     |          Array: the sum as a new array.
     |  
     |  __rmul__(self, other)
     |      Element-wise multiplies this Array with a number or array.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to multiply element-wise to this array.
     |      
     |      Returns:
     |          Array: a new array with every element multiplied with `other`.
     |  
     |  __rsub__(self, other)
     |      Element-wise subtracts this Array from a number or Array.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number being subtracted from.
     |      
     |      Returns:
     |          Array: the difference as a new array.
     |  
     |  __str__(self)
     |      Returns a nicely printable string representation of the array.
     |      
     |      Returns:
     |          str: A string representation of the array.
     |  
     |  __sub__(self, other)
     |      Element-wise subtracts an Array or number from this Array.
     |      
     |      If the method does not support the operation with the supplied arguments
     |      (specific data type or shape), it should return NotImplemented.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to subtract element-wise from this array.
     |      
     |      Returns:
     |          Array: the difference as a new array.
     |  
     |  is_equal(self, other)
     |      Compares an Array element-wise with another Array or number.
     |      
     |      If `other` is an array and the two array shapes do not match, this method should raise ValueError.
     |      
     |      Args:
     |          other (Array, float, int): The array or number to compare with this array.
     |      
     |      Returns:
     |          Array: An array of booleans with True where the two arrays match and False where they do not.
     |                 Or if `other` is a number, it returns True where the array is equal to the number and False
     |                 where it is not.
     |      
     |      Raises:
     |          ValueError: if the shape of self and other are not equal.
     |  
     |  mean(self)
     |      Computes the mean of the array
     |      
     |      Only needs to work for numeric data types.
     |      
     |      Returns:
     |          float: The mean of the array values.
     |  
     |  min_element(self)
     |      Returns the smallest value of the array.
     |      
     |      Only needs to work for numeric data types.
     |      
     |      Returns:
     |          float: The value of the smallest element in the array.
     |  
     |  variance(self)
     |      Computes the variance of the array
     |      
     |      Only needs to work for numeric data types.
     |      The variance is computed as: mean((x - x.mean())**2)
     |      
     |      Returns:
     |          float: The mean of the array values.
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |  
     |  __hash__ = None

FUNCTIONS
    asarray(a)
        Cast an object to an array
        
        Parameters:
            a (Array, iterable): An array or any array-like container.
        
        Returns:
            Array:
                a, cast as an Array.
                If a is already an Array, return it.

DATA
    __all__ = ['Array', 'asarray']

FILE
    /home/dokken/Documents/src/UiO/UiO-IN3110.github.io/lectures/production/sphinx-docs/package_to_document/src/myarray/__init__.py


(UIO-IN3110) 
python3 -c "import myarray; help(myarray.Array.mean)"
Help on function mean in module myarray.myarray:

mean(self)
    Computes the mean of the array
    
    Only needs to work for numeric data types.
    
    Returns:
        float: The mean of the array values.

(UIO-IN3110) 

Let’s get started by creating a new file int he source directory, with source/api.rst:

My Array API
============

.. automodule:: myarray

and we can try to build the documentation again with make html. We get the following warning

docs/api.rst: WARNING: document isn't included in any toctree

Note

Inspect the package myarray, both __init__.py and myarray.py contains __all__ = [...] statements. This is to instruct Python (and autodoc) what members should be included in an import and in documentation. As we have created the myarray-package, the module is described by what is in the __init__.py file. We could also document the module myarray.myarray explicitly.

head -n5 package_to_document/src/myarray/__init__.py
from myarray.myarray import Array, asarray

__all__ = ["Array", "asarray"](UIO-IN3110) 
head -n8 package_to_document/src/myarray/myarray.py
"""
Defines an Array class.

Class template for Arrays, IN 3110 assignment 3
"""

__all__ = ["Array", "asarray"]

(UIO-IN3110) 

The toctree (table of contents tree) warning is because our new api.rst document is not included in any .. toc:: directives. Sphinx wants to make sure that you can navigate through all the documentation via table of contents, so it encourages you to include all your documents in

We can add our new document to the top-level table of contents in index.rst:

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   api.rst

No we can build again with make html and open _build/html/api.html.

Note

To build the documentation for a module, it has to be installed on your system. As myarray is a proper package, we use python3 -m pip install /path/to/myarray to install it prior to building with sphinx.

If you really want to avoid making a package, one can modify the sys.path inside the conf.py file. However, this is not recommended!

Next, we can add our function to the documentation:

My Array API
============

.. automodule:: myarray

.. autofunction:: asarray

and build again with make html, reloading _build/html/api.html

Similarly, we can document our Array class:

My Array API
============

.. automodule:: myarray

.. autofunction:: asarray

.. autoclass:: Array

now all our top-level items are documented, but we probably want to see our member methods, not just the constructor.

For that, we add the :members: option to autoclass:

My Array API
============

.. automodule:: myarray

.. autofunction:: asarray

.. autoclass:: Array
    :members:

And finally, since we are most interested in “special” methods (__mul__ and friends), we may want to document the special methods as well, which are not included by default in :members::

My Array API
============

.. automodule:: myarray

.. autofunction:: asarray

.. autoclass:: Array
    :members:
    :special-members:

Now we are done setting up our API docs. autodoc has lots of options for controlling which members are

The last step we are going to do is enable another extension called napoleon, which understands special docstring formats, which are widely used. These give nice highlighting and linking for parameters, return types, etc.

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

Now rebuild, and see how much nicer the output is!