Autodocumenting your Python code with Sphinx - part 1

 Jan. 12, 2016     0 comments

There are a number of Python auto-documenting tools in existence, starting with Python's built-in pydoc module. Some time ago Epydoc was a popular tool for auto-generating documentation from Python docstrings but its development was abandoned and it does not support current Python releases, though some popular Python IDEs, like PyDev for Eclipse or PyCharm still support Epytext markup language.

Now Sphinx has become de facto the standard tool for creating documentation for Python programs and libraries, and even the docs for Python itself are created with Sphinx. Docs for third-party Python libraries hosted on Read the Docs, a popular documentation hosting site, are also generated by Sphinx.

The problem is that Sphinx is created primarily for writing textual documentation, and auto-generating documentation from Python code is a bolt-on feature in that is neither convenient nor intuitive. And the official Sphinx documentation does not help much. Most of the Sphinx tutorials available in the Internet are also focused on writing documentation and not on auto-documenting Python code. BurntSushi who created pdoc — another documentation generator for Python — even complained that he could not make Sphinx work as documentation generator for Python code, so he had to create its own.

My goal in this tutorial is to give you enough info to get you started with auto-documenting your Python programs/libraries. It is not supposed to cover all bases, and for more advanced topics please see Sphinx documentation and other sources.

Writing Python Docstrings for Sphinx

Sphinx generates documentation from plain text written using reStructuredText (reST for short) markup language. There are a number of reST guides in the Net, so I won't cover it here. I assume that you are familiar at least with reST basics.

General rule for documenting a Python object is that its docstring should start with object's short description followed by a blank line followed by detailed description. Don't use something like "foo module" for a short description, make it informative, e.g. "Functions and classes for processing images".

Advanced Python IDEs, like PyCharm or PyDev support Sphix/reST markup. E.g. in PyCharm go to Settings > Tools > Python Integrated Tools and select reStructuredText from Docstring format drop-down list. Now PyCharm will help you to write Sphinx-compatible docs by generating docstring stubs from objects' signatures, providing completion for reST directives and inspections that warns you if your object's signature and its parameters list in docstring do not match.

Now let's take a look at various Python objects.

Vairables

Sphinx supports 3 methods of marking documentation for variables: a comment + colon #: before a variable, a comment + colon at the same line with a variable and a block docstring """...""" after a variable. Each variant will do.

Example:

 Click to show
#: Pi constant
PI = 3.141592654
E = 2.718281828459  #: e constant
example = PI * E
"""Another example of variable documentation"""

Functions

Function docstrings should follow the pattern described above. In addition to that Sphinx supports several special directives for pretty formatting function's description. The most common directives are: :param, :type, :return, :rtype and :raises. There are more but let's stick to the basics. The usage of those directives is better demonstrated by an example:

 Click to show
def division(divident, divisor):
    """
    Division function

    This is an example of function documentation.
    It illustrates how to document parameters, return values
    and their types, and also the exception that a function
    or a module may raise under certain conditions.

    :param divident: operation divident
    :type divident: float
    :param divisor: operation divisor
    :type divisor: float
    :return: division result
    :rtype: float
    :raises ZeroDivisionError: when divisor = 0

    .. note:: This function can accept :class:`int` parameters too.

    .. warning:: ``divisor=0`` will cause :exc:`ZeroDivisionError` exception!

    Example::

        result = division(a, b)
    """
    return divident / divisor

(The example is pretty abstract but it does the job). :type: and :rtype: directives are optional and can be omitted. 2 colons after "Example"  is a general reST marker for the following pre-formatted code. Note the usage of :class: directive to reference another class (in this case an exception). Sphinx supports several directives for cross-referencing Python objects, but you need to enable this feature during the initial Sphinx project configuration (more about this in Part 2). Note that, except for built-in Python names and names located in the same module, you need to provide a fully qualified object's name for cross-reference, e.g. :class:`foo.bar.Baz` denotes a class Baz located in bar module inside foo package.

If you enable cross-referencing Python objects, it works for :type: and :rtype: directives too. You need to use fully qualified names for cross-referencing here too.

Update on 2020-06-03: Sphinx 3.x fully supports Python 3 type annotations so :type: and :rtype: directives can be omitted in favor of type annotations. Type annotations work for variables too.

Classes And Methods

Classes and methods follow the same basic rules as functions: a short description then more detailed description with possible parameters, return values, exceptions etc. Again, you can use all reST formatting directives. By default Sphinx does not document "private" attributes and methods, including __init__. There are a couple workarounds for including __init__ in generated documentation but I'd recommend to put all information about class instantiataion (parameters, possible exceptions, usage examples) in the class-level docstring.

Methods are no different from functions. Note that properties are documented as single entities, no matter how many methods implement them.

Below is an example of class documentation:

 Click to show
import tkinter as tk
import tkinter.ttk as ttk


class Application(tk.Frame):
    """
    A very simple GUI application

    This example illustrates writing docstrings for a class.

    :param master: a master Tkinter widget (opt.)

    Example::

        app = Application()
    """
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack(padx=5, pady=5)
        self._create_widgets()

    def _create_widgets(self):
        self._label = ttk.Label(self, text='Hello World!', width=30, anchor='center')
        self._label.pack()
        self._quit_btn = ttk.Button(self, text='QUIT', command=root.destroy)
        self._quit_btn.pack()

    def get_text(self):
        """
        Get label text

        This is an example of a method docstring

        :return: label text
        :rtype: str
        """
        return self._label['text']

    def set_text(self, value):
        """
        Set label text

        This is another example of a method docstring

        :param value: new label text
        :type value: str
        """
        self._label['text'] = value

Again, the example is quite abstract (it's a slightly modified example from the Python official docs for Tkinter) and does not follow coding best practices ("getters" and "setters" are better be implemented as properties) but my goal is to show you how to write Sphinx-compatible docstrings.

The full example module combined from the code above you can find here in my example Sphinx project on Github. And here is a web-page generated from this module by Sphinx (Alabaster theme).

This is the end of Part 1 of this tutorial. In Part 2 I will show you how to set-up Sphinx to generate documentation from properly documented Python code.

Autodocumenting your Python code with Sphinx - part 2

Useful Links

Sphinx official site.

reStructuredText cheat-sheet.

Another reStructuredText cheat-sheet.

  Python