Python Documentation

Documentation is absolutely essential to anyone seeking to use your software. The easiest and most reliable way to produce this documentation is by using a tool that examines the source itself and develops the document directly from the programs. By using cleverly formatted Python docstrings, we can augment that analysis with easy-to-understand descriptions.

In the case of Python, there are several tools for extracting documentation from the source. The most popular one is Sphinx, http://sphinx.pocoo.org/.

We can use pydoc, also. This is part of Python. It doesn’t cope well with files in UTF-8 encoding. You need to include the following comment in any files that have Unicode characters.

# -*- coding: utf-8 -*-

In Documentation Development we’ll look at how we can use Sphinx to create complete, accurate, and nice-looking documentation for our application.

In Using Sphinx we’ll look at a aspects of using Sphinx.

In order to use either tool, we’ll need to write using ReStructuredText (RST.) In Basic RST Markup we’ll look at the foundations of using RST.

In Interpreted Text, or Inline Markup we’ll look at inline markup that produces things like emphasis and strong text.

We’ll look at how we can provide details of function arguments and return values in RST Field Markup. This is primarily focused on Sphinx.

We’ll look at more complex RST markup in Directives

In Standard Paragraphs we’ll look at boilerplate like “see also”, and “warning” sections that are often marked with special icons aor distinctive layout.

We’ll show a large example in Class Example.

Documentation Development

Generally, the workflow has the following outline.

  1. Develop the skeleton class.

  2. Develop unit tests for most features.

  3. Rework the class until it passes the unit tests. This may involve adding or modifying tests as we understand the class better. This also involves writing docstrings for modules, classes and methods.

  4. Revisit the modules, classes and methods, finishing each description using the ReStructured Text (RST) markup language. We won’t cover all of this language, just enough to provide a sense of how the tool is used to make clean, professional API documentation.

  5. Run the documentation tool (i.e. sphinx-build) to create the documentation. Fix any errors in your docstring markup. Also, rework the documentation as necessary to be sure that you’ve capture important information about the class, the methods, the design decisions.

Using Sphinx

When you install Sphinx, it creates a script that you can use.

Generally, you’ll run the $ sphinx-quickstart script to create an empty documentation directory.

$ sphinx-quickstart

This interactive script will ask a number of questions, allowing you to pick sensible directories and file names.

You’ll need to include two extensions to enable Sphinx to create your API documentation.

  • autodoc will create module documents based on the RST markup in the docstrings.

  • autosummary will create summaries of packages and modules.

You’ll then add content to your index.rst file. This file is the root for the documentation. It can be very simple, or or it can do include other files to break a large document into manageable pieces.

The most important commands are the following.

To run Sphinx, you’ll use a command similar to the following.

$ sphinx-build -b html sourcedir builddir

Basic RST Markup

We write our module, class and method docstrings using the RST markup language. RST is one of many structured text markup languages, where indentation and formatting rules are used to interpret your documentation. These formatting rules help the tools deduces the structure of your document (sections, list, etc.) and the presentation (bold, italic, or font changes.) Some of these markup rules have been used in plain text documents since documents were first stored on computers.

Fundamentally, the RST formatting rules are implemented by a module named docutils. For additional documentation on RST, see the Sphinx web site (http://sphinx.pocoo.org) and the Docutils web site (http://docutils.sourceforge.net/).

In addition to document structure, deduced from the layout, RST also uses explicit directives and interpreted text (or “inline”) markup.

Document Structure. Generally, you must write in paragraphs. A blank line is the end of one paragraph and the beginning of another. Also, different indentation will signal paragraph changes. The indent rule is handy for making bullet lists or numbered lists.

A line that is indented and begins with - is a bulleted list.

A line that starts with digits and periods is a numbered list. Lists can nest within each other.

If you need to have sections and subsections (you’ll almost always do this in the docstring for a module or package), you “underline” the title with lines of ===, --- or ~~~.

To include code code samples and doctest, you’ll use either the :: marker. Or the explicit ..  code-block:: directive.

Generally, the first paragraph must be a pithy summary of the material to follow. This paragraph will often be used in overviews and index pages.

Example testCard Module. For example, our module document might look like this.

#!/usr/bin/env python
"""The testCard module includes a number of unit tests for the Card
and Deck classes in the card module.

Overview
========

This module tests the following classes:
    - Card
    - Deck

Card Tests
----------

..  autoclass:: TestCards

Deck Tests
----------

..  autoclass:: TestDeck

Usage
=====

This module uses the standard text runner, so it can be executed
from the command line as follows::

    python testcard.py
"""
  1. We started with an overview paragraph. It summarizes the module in a single sentence.

  2. We have an underlined section heading. In this document, the highest level of the outline is underlined with =’s. The second level is underlined with -.

    RST is very flexible with the underlining rules.

  3. We have a bullet list. Simply indent, and begin the paragraph with -. Lists can be nested, the RST processor will work this out based on the indentation of the various sublists.

  4. We used the ..   autoclass:: directive to gather docstring documentation from the class docstring and include it in the overall module docstring.

  5. We included a literal block of code after a paragraph which ends with ::. The following material is indented to show what should be formatted specially.

Interpreted Text, or Inline Markup

In addition to basic document structure rules, RST includes a way to show that a span of characters should be interpreted specially. This is sometimes called Inline Markup because it is within the structural markup.

  • *words* will render the words in italic. This can also be done with :emphasis:`words`.

  • **words** will render the words in bold. This can also be done with :strong:`words`.

  • ``words`` will render the words in a fixed font to indicate that it is a code sample. Literal programming text can also use :literal:`words`.

    Code samples can also be done with :samp:`word {replaceable}`. In this case, the material enclosed in {} is displayed in italics: word replaceable.

Additionally, there are several cross-reference inline markup formats available for code elements.

Here’s an example of a paragraph using some inline markup to make some characters italic, other bold, and still others a fixed-width find that makes them look like code.

This method computes the square root of a number, *n*.
It returns a value, *r*, such that :samp:`r**2 == n`.

**Note**.  Square root of a negative number will raise a ``TypeError`` exception.

RST Field Markup

RST Field markup is primarily used to tie your documentation directly to Python language structures. Field markup is a separate paragraph of the form :tag arg: body. We won’t cover all of the tags, just enough to produce basic documentation.

Field markup also provides several standard kinds of standard “formal paragraphs” within your document. We’ll look at these separately.

The following tags are used to define detailed features of a method function.

  • :param p: description A description of the parameter, p, for a function or method.

  • :return: description A description of the return value from the function or method.

  • :keyword k: description A description of the keyword parameter, k, for a function or method.

Here’s an example of a method function docstring that uses @param tags.

def __init__( self, rank, suit ):
    """Build a card with a given rank and suit.

    :param rank: numeric rank, ace is 1, 2-13 for 2 though King.
    :param suit: String suit, use one of the module constants.
    """
    self.rank= 14 if rank == 1 else rank
    self.suit= suit

The following tags are used to define specific variables in a module or class.

  • :ivar v: description A description of the an instance variable of a class. Generally, this will be in the class level docstring, and will refer to one of the class’s self. variables.

  • :var v: description A global variable of a module. Generally, this will be in the module docstring, and will refer to one of the global variables created by the module.

Here’s an example of a class definition docstring that uses :ivar: tags.

class Card:
    """A single playing card, suitable for Blackjack or
    Poker.  While a suit is retained, it doesn't figure into
    the ordering of cards, as it would in Bridge.

    **Reminder**. The internal rank of an Ace is 14.  The constructor,
    however, expects a value of 1.

    :ivar rank: the numeric rank of the card.  2-13, with ace as 14.
    :ivar suit: the string suit of the card.
    """

Directives

RST has a number of useful directives available for structuring the documentation. Sphinx uses all of the basic RST directives and adds a large number of additional directives.

A directive is an RST markup command starts with a special .. line, and may include a number of lines indented within the directive. One of the common ones is the .. code-block:: directive. The :: is an essential part of the directive syntax.

It looks like this.

Example.

..  code-block:: python

    def fact(n):
        if n == 0: return 1
        return n*fact(n-1)

More Text.

The directive starts with the .. line and continues through the indented section. The directive content ends with the end of the indentation.

Epydoc uses the :tag: syntax; it doesn’t define new RST directives. Because of this, there are two styles for some of the more advanced markup: an Epydoc tag style and a Sphinx directive style.

Standard Paragraphs

There are several kinds of standard paragraphs that are part of any well-written document set. These include things like

  • Related Topics.

    Epydoc usess the :seealso: tag to generate a “Related Topics” paragraph with references to other documents.

    Sphinx uses the ..  seealso:: directive line to generate additional references.

  • Admonitions (Notes, Warnings, etc).

    Epydoc uses tags like :note:, :attention:, :bug:, and :warning: tags to generate standard types of admonition paragraphs.

    Sphinx uses directives like ..  note::, ..  attention::, and ..  warning:: directives to generate standard types of admonition paragraphs. Other admonitions include “caution”, “danger”, “error”, “hint”, “important”, “tip”.

  • Status. You can track the development and deployment status of your programs. Tags like :version:, :todo:, :deprecated:, :since:, :status:, and change: are used by both Epydoc and Sphinx.

  • Bibliographic Information. You can provide standard publication information. Tags like :author:, :organization:, :copyright:, :license: and :contact: can be used.

Class Example

Here’s an example of a module with detailed class documentation.

blackjack.py

#!/usr/bin/env python3
"""
Building Skills in Object-Oriented Design V4

The blackjack module includes the Suit class and Card class hierarchy.

:author: S. Lott
:license: http://creativecommons.org/licenses/by-nc-nd/3.0/us/
"""
from typing import Any
import enum


class Suit(enum.Enum):
    """Enumerated suit names and values."""

    Clubs = u"\N{BLACK CLUB SUIT}"
    Diamonds = u"\N{WHITE DIAMOND SUIT}"
    Hearts = u"\N{WHITE HEART SUIT}"
    Spades = u"\N{BLACK SPADE SUIT}"


class Card:
    """A single playing card, suitable for Blackjack or
    Poker.  While a suit is retained, it doesn't figure into
    the ordering of cards, as it would in Bridge.

    ..  note:: Aces and Facecards.
        Ace and Facecards are separate subclasses.

    ..  attribute:: rank

        The numeric rank of the card.  2-13,  ace has an effective
        rank of 14 when used in Poker.

    ..  attribute:: suit

        The string suit of the card.  This should be from the
        named constants (Clubs, Diamonds, Hearts, Spades).

    At the class level, there are four constants that can
    make code look a little nicer.

    :var: Jack
    :var: Queen
    :var: King
    :var: Ace
    """

    Jack = 11
    Queen = 12
    King = 13
    Ace = 1

    def __init__(self, rank: int, suit: Suit) -> None:
        """Build a card with a given rank and suit.

        :param rank: numeric rank, 2-10.  Aces and FaceCards are separate.
        :type rank: integer in the range 2 to 10 inclusive.

        :param suit:  suit, a value from the Suit enum
        :type suit: Suit
        """
        assert isinstance(suit, Suit)
        self.rank = rank
        self.suit = suit
        self.points = rank

    def hardValue(self) -> int:
        """For blackjack, the hard value of this card.

        :returns: int
        """
        return self.points

    def softValue(self) -> int:
        """For blackjack, the soft value of this card.

        :returns: int
        """
        return self.points

    def __eq__(self, other: Any) -> bool:
        """Compare cards, ignoring suit.

        >>> from blackjack_doc import Card, Suit
        >>> Card(2, Suit.Diamonds) == Card(2, Suit.Spades)
        True
        >>> Card(2, Suit.Diamonds) == Card(10, Suit.Spades)
        False
        """
        return self.rank == other.rank

    def __lt__(self, other: Any) -> bool:
        """Compare cards, ignoring suit.

        >>> from blackjack_doc import Card, Suit
        >>> Card(2, Suit.Diamonds) < Card(3, Suit.Spades)
        True
        >>> Card(10, Suit.Diamonds) < Card(10, Suit.Spades)
        False
        """
        return self.rank < other.rank

    def __le__(self, other: Any) -> bool:
        return self.rank <= other.rank

    def __gt__(self, other: Any) -> bool:
        return self.rank > other.rank

    def __ge__(self, other: Any) -> bool:
        return self.rank >= other.rank

    def __str__(self) -> str:
        """
        >>> from blackjack_doc import Card, Suit
        >>> str(Card(2, Suit.Diamonds))
        ' 2♢'
        """
        return f"{self.rank:2d}{self.suit.value}"

    def __repr__(self) -> str:
        """
        >>> from blackjack_doc import Card, Suit
        >>> repr(Card(2, Suit.Diamonds))
        "Card(rank=2, suit=<Suit.Diamonds: '♢'>)"
        """
        return f"{self.__class__.__name__}(rank={self.rank!r}, suit={self.suit!r})"

We’ve included a module-level docstring, class-level docstrings, and even some method-level docstrings. The idea is to provide enough information so that the automatic documentation produced by Sphinx is complete and useful.

Example Sphinx Automodule

This shows how the documentation looks when .. automodule:: is used.

Building Skills in Object-Oriented Design V4

The blackjack module includes the Suit class and Card class hierarchy.

author
  1. Lott

license

http://creativecommons.org/licenses/by-nc-nd/3.0/us/

class blackjack_doc.Card(rank: int, suit: blackjack_doc.Suit)

A single playing card, suitable for Blackjack or Poker. While a suit is retained, it doesn’t figure into the ordering of cards, as it would in Bridge.

Note

Aces and Facecards. Ace and Facecards are separate subclasses.

rank

The numeric rank of the card. 2-13, ace has an effective rank of 14 when used in Poker.

suit

The string suit of the card. This should be from the named constants (Clubs, Diamonds, Hearts, Spades).

At the class level, there are four constants that can make code look a little nicer.

Var

Jack

Var

Queen

Var

King

Var

Ace

__eq__(other: Any) → bool

Compare cards, ignoring suit.

>>> from blackjack_doc import Card, Suit
>>> Card(2, Suit.Diamonds) == Card(2, Suit.Spades)
True
>>> Card(2, Suit.Diamonds) == Card(10, Suit.Spades)
False
__ge__(other: Any) → bool

Return self>=value.

__gt__(other: Any) → bool

Return self>value.

__init__(rank: int, suit: blackjack_doc.Suit) → None

Build a card with a given rank and suit.

Parameters
  • rank (integer in the range 2 to 10 inclusive.) – numeric rank, 2-10. Aces and FaceCards are separate.

  • suit (Suit) – suit, a value from the Suit enum

__le__(other: Any) → bool

Return self<=value.

__lt__(other: Any) → bool

Compare cards, ignoring suit.

>>> from blackjack_doc import Card, Suit
>>> Card(2, Suit.Diamonds) < Card(3, Suit.Spades)
True
>>> Card(10, Suit.Diamonds) < Card(10, Suit.Spades)
False
__repr__() → str
>>> from blackjack_doc import Card, Suit
>>> repr(Card(2, Suit.Diamonds))
"Card(rank=2, suit=<Suit.Diamonds: '♢'>)"
__str__() → str
>>> from blackjack_doc import Card, Suit
>>> str(Card(2, Suit.Diamonds))
' 2♢'
__weakref__

list of weak references to the object (if defined)

hardValue() → int

For blackjack, the hard value of this card.

Returns

int

softValue() → int

For blackjack, the soft value of this card.

Returns

int

class blackjack_doc.Suit

Enumerated suit names and values.

Looking Forward

In these first few chapters we’ve done several things:

  • Identified the problem we’re going to solve,

  • Installed the tools to help build a trustworthy Python application,

  • Made sure we can write unit tests for our code,

  • Started down the path of producing useful documentation.

In the next chapter, we’ll start the journey of building a simulation for the Roulette game. The first chapter has a close look at the game. After that, we’ll look at the kind of solution we’ll build, and in the remaining chapters, we’ll look at the individual classes required.