Python API documentation generation

This theme includes an optional extension that generates Python API documentation pages.

  • A separate page is generated for each documented entity.

  • Top-level module-level entities are organized into groups, specified by the :group: field within the docstring. The python-apigen-group directive is used to insert a summary of a group into an existing page.

  • The summary of a group shows an abbreviated signature and an abbreviated description for each entity in the group; the name portion of the signature is a link to the separate page for that entity.

  • Class members are also organized into groups, also using the :group: field within their docstrings. Class members without a group are assigned to a default group based on their name. The summaries for each class member group are included on the page that documents the class.

  • There is special support for pybind11-defined overloaded functions. Each overload is documented as a separate function; each overload is identified by the :overload: field specified within its docstring.

Usage

To use this extension, add "sphinx_immaterial.apidoc.python.apigen" to the list of extensions in conf.py and define the python_apigen_modules configuration option.

For example:

extensions = [
    # other extensions...
    "sphinx_immaterial.apidoc.python.apigen",
]

python_apigen_modules = {
      "my_module": "api",
}

This configuration is sufficient to generate a documentation page for every top-level member of the specified modules. To generate a listing of those top-level members and add the generated documentation pages to the global table of contents, the python-apigen-group directive may be used.

Tip

This extension works well with the toc_title_is_page_title configuration option.

Groups

Each documented module member and class member is assigned to a member group. The group is either explicitly specified or determined automatically.

The group may be specified explicitly using the :group: field within the docstring:

def foo(x: int) -> int:
    """Does something or other.

    :param x: The parameter.
    :group: My group

When using the sphinx.ext.napoleon extension, the group can also be specified using the Group: section:

def foo(x: int) -> int:
    """Does something or other.

    Args:
      x: The parameter.

    Group:
      My group

If the group is not specified explicitly, it is determined automatically based on the python_apigen_default_groups option.

Member order

For each each member there is also assigned an associated integer order value that determines the order in which members are listed: members are listed in ascending order by their associated order value, and ties are broken according to the python_apigen_order_tiebreaker option.

Similarly to the group name, the order value may be specified explicitly using the :order: field within the docstring:

def foo(x: int) -> int:
    """Does something or other.

    :param x: The parameter.
    :group: My group
    :order: 1

When using the sphinx.ext.napoleon extension, the group can also be specified using the Order: section:

def foo(x: int) -> int:
    """Does something or other.

    Args:
      x: The parameter.

    Group:
      My group

    Order:
      1

If the order value is not specified explicitly, it is determined automatically based on the python_apigen_default_order option.

The order associated with a member determines both:

  • the relative order in which the member is listed within its associated group;

  • for class members, the order of the per-group sections for which an explicit section is not already listed in the class docstring (the order value associated with a group is the minimum of the order values of all of its members).

rST Directives

.. python-apigen-group:: group-name

Generates a summary of all top-level members that are in the specified group, and also inserts a table-of-contents entry for each member at the current document position.

Before matching the specified group name to the group name of every top-level member, all the group names are normalized by converting each letter to lowercase and converting spaces to -.

:notoc:

By default, this directive also adds the pages corresponding to the members of the specified group to the global table of contents as children of the current page/section. Specifying this flag disables that behavior.

The group namespace for module-level members is global: if module a defines a member foo in group My group and module b defines a member bar that is also in My group, then the following example would insert a summary of both a.foo and b.bar:

Example usage
.. python-apigen-group:: Some other group
   :notoc:
class tensorstore_demo.VeryLongClassNameForTestingOutWordWrapping[source]

This is a class with a very long name.

class tensorstore_demo.Foo[source]

This is a class defined in the tensorstore_demo._tensorstore module but should appear to be defined in the tensorstore_demo module.

tensorstore_demo.bar(x: Foo-> Foo[source]
tensorstore_demo.bar_also(x: Foo-> Foo

Returns x.

class tensorstore_demo.FooSubclass(Foo)[source]

This is a subclass of Foo.

Note

This directive only includes top-level module members (for the modules specified by python_apigen_modules). Class members are also organized into groups, but these groups are per-class and are listed (along with their members) on the documentation page for the class.

.. python-apigen-entity-summary:: entity-name

Generates a summary of a single Python entity.

The entity-name should be specified as module_name.ClassName.member or module_name.ClassName.member(overload).

:notoc:

By default, this directive also adds the page corresponding to the specified Python entity to the global table of contents as a child of the current page/section. Specifying this flag disables that behavior.

Example usage
.. python-apigen-entity-summary:: tensorstore_demo.IndexDomain.__init__(json)
   :notoc:
tensorstore_demo.IndexDomain(*json: Any)

Constructs an index domain from its JSON representation.

Sections defined within docstrings

When using this extension, docstrings can define sections, including nested sections, using the usual reStructedText section syntax. The mapping between punctuation characters and heading levels is local to the individual docstring. Therefore, it is not necessary (though still recommended) to use a consistent order of punctuation characters across different docstrings.

In addition to providing a way to organize any explanatory content, for classes, sections can also correspond to member groups, as described below.

Class member groups

Within the documentation page for a given class, after its normal docstring content, a summary is added for each documented member, with a separate section per group.

For each group, if the class docstring defines a section with a title equal to the group name (or an id equal to the normalized group name), the member summaries are added to the end of the existing section. Otherwise, a new section for the group is added to the end of the class documentation.

New sections are added in the order of first ocurrence of the group within the order defined for the members.

For example, consider the following class definition:

class Foo:
    """This is some class.

    Constructors
    ------------

    This class defines the following constructors.

    Operations
    ----------

    This class supports the following operations.
    """

    def __init__(self):
        """Constructs the class.

        :group: Constructors
        """

    def foo(self):
        """Performs the foo operation.

        :group: Operations
        """

    def bar(self):
        """Performs the bar operation.

        :group: Operations
        """

    def size(self) -> int:
        """Returns the size.

        :group: Accessors
        :order: 2
        """

    def __setitem__(self, i: int, value: int) -> None:
        """Set the element at the given position.

        :group: Indexing
        :order: 3
        """

    def __getitem__(self, i: int) -> int:
        """Returns the element at the given position.

        :group: Indexing
        :order: 1
        """

The __init__ method will be documented within the existing Constructors section, the foo and bar methods will be documented within the existing Operations section. After the Operations section, a new Indexing section will be added that lists the __getitem__ and __setitem__ members, and then a new Accessors section will be added that lists the size method.

Configuration

python_apigen_modules : dict[str, str] = {}

Maps module names to the output path prefix relative to the source directory.

All entities defined by the specified modules are documented.

For example, with the following added to conf.py:

python_apigen_modules = {
    "my_module": "my_api/",
    "my_other_module": "other_api/my_other_module.",
}

The following generated documents will be used (depending on the value of python_apigen_case_insensitive_filesystem):

Python object

Overload

Document (case-sensitive)

Document (case-insensitive)

my_module.foo

my_api/foo

my_api/foo-2c26b46b

my_module.Foo

my_api/Foo

my_api/Foo-1cbec737

my_module.Foo.method

my_api/Foo.method

my_api/Foo.method-14e51ffc

my_module.Foo.__init__

json

my_api/Foo.__init__-json

my_api/Foo.__init__-json-3916c576

my_module.Foo.__init__

values

my_api/Foo.__init__-values

my_api/Foo.__init__-values-e6f37aec

my_module.Bar

my_api/Bar

my_api/Bar-95d64cac

my_other_module.Baz

other_api/my_other_module.Baz

other_api/my_other_module.Baz-e8f1251a

Note

The specified path prefix for each module is treated as a prefix, not a directory. It should normally end in either "/" or some other delimiter like "." If you want the generated document name to include the module name, choose a prefix of the form "api_directory/module_name.". If you want the generated document name to exclude the module name, choose a prefix of the form "api_directory/".

Warning

Because Sphinx is not designed to process files outside the source tree, these files are actually written to the source tree, and are regenerated automatically at the start of the build. These files should not be checked into your source repository. (When using git, you may wish to add a suitable pattern to a .gitignore file.)

The generated files start with a special comment to indicate that they were generated by this extension. Stale files from previous build invocations are deleted automatically. If there is an existing non-generated file with the same name as a to-be-generated file, the existing file will not be overwritten and the build will fail (showing an error message).

python_apigen_default_groups : list[tuple[str, str]] = [('.*', 'Public members'), ('class:.*', 'Classes')]

list of (pattern, group) pairs, where pattern is a regular expression matching strings of the form "<objtype>:<fully_qualified_member_name>" (e.g. "method:module_name.ClassName.member") and group is the group name to assign.

The group name for a given member is determined by the last matching pattern. If no pattern matches, the group is Public members.

Example addition to conf.py
python_apigen_default_groups = [
    ("class:.*", "Classes"),
    (r".*\.__(init|new)__", "Constructors"),
    (r".*\.__(str|repr)__", "String representation"),
]
python_apigen_default_order : list[tuple[str, int]] = []

list of (pattern, order) pairs, where pattern is a regular expression matching strings of the form "<objtype>:<fully_qualified_member_name>" (e.g. "method:module_name.ClassName.member") and order is the int order to assign.

The order value for a given member is determined by the last matching pattern. If no pattern matches, the order value is 0.

Example addition to conf.py
python_apigen_default_order = [
    ("class:.*", -10),
    (r".*\.__(init|new)__", -5),
    (r".*\.__(str|repr)__", 5),
]
python_apigen_order_tiebreaker : 'definition_order' | 'alphabetical' = 'definition_order'

Specifies the relative order of members that have the same associated order value.

"definition_order"
  • Top-level members are sorted first by the order their containing module is listed in python_apigen_modules and then by the order in which they are defined.

  • Class members are sorted by the order in which they are defined. Inherited members are listed after direct members, according to the method resolution order.

"alphabetical"

All members are sorted alphabetically, first using case-insensitive comparison and then breaking ties with case-sensitive comparison.

Add to conf.py to specify alphabetical order.
python_apigen_order_tiebreaker = "alphabetical"
python_apigen_case_insensitive_filesystem : None | bool = None

This extension results in an output file for each documented Python object based on its fully-qualified name. Python names are case-sensitive, meaning both foo and Foo can be defined within the same scope, but some filesystems are case insensitive (e.g. on Windows and macOS), which creates the potential for a conflict.

By default (if python_apigen_case_insensitive_filesystem is None), this extension detects automatically if the filesystem is case-insensitive, but detection is skipped if the option is set to an explicit value of True or False:

Add to conf.py to force case-insensitive naming scheme
python_apigen_case_insensitive_filesystem = True

If the filesystem is either detected or specified to be case-insensitive, case conflicts are avoided by including a hash in the document name.

python_apigen_rst_prolog : str = ''

A string of reStructuredText that will be included at the beginning of every docstring.

This may be used to set the rST directive default-role, highlight language, or default-literal-role.

Note

The prior default role, default literal role, and default highlight langauge are automatically restored after processing the python_apigen_rst_epilog. Therefore, it is not necessary to manually add anything to python_apigen_rst_epilog to restore the prior roles or highlight language.

Setting default roles and highlight language in conf.py
rst_prolog = """
.. role python(code)
   :language: python
   :class: highlight
"""

python_apigen_rst_prolog = """
.. default-role:: py:obj

.. default-literal-role:: python

.. highlight:: python
"""
python_apigen_rst_epilog : str = ''

A string of reStructuredText that will be included at the end of every docstring.

This option is supported for symmetry with python_apigen_rst_prolog, but in most cases is not needed because any changes to the default role, default literal role, and default highlight language due to python_apigen_rst_prolog are undone automatically.

Subscript methods

Subscript methods are attributes defined on an object that support subscript syntax. For example:

arr.vindex[1, 2:5, [1, 2, 3]]

These subscript methods can be implemented as follows:

class MyArray:
    class _Vindex:
        def __init__(self, arr: MyArray):
            self.arr = arr

        def __getitem__(self, sel: Selection):
            # Do something with `self.arr` and `sel`.
            return result

    @property
    def vindex(self) -> MyArray._Vindex:
        return MyArray._Vindex(self)

Based on the python_apigen_subscript_method_types option, this extension can recognize this pattern and display vindex as:

vindex[sel: Selection]

rather than as a normal property.

python_apigen_subscript_method_types : re.Pattern = '.*\\._[^.]*'

Regular expression pattern that matches the return type annotations of properties that define subscript methods.

Return type annotations can be specified either as real annotations or in the textual signature specified as the first line of the docstring.

The default value matches any name beginning with an underscore, e.g. _Vindex in the example above.

python_apigen_show_base_classes : bool = True

Display the list of base classes when documenting classes.

Unlike the built-in sphinx.ext.autodoc module, base classes are shown using the normal Python syntax in a parenthesized list after the class name.

The list of base classes displayed for each class can be customized by adding a listener to the autodoc-process-bases event. This is useful for excluding base classes that are not intended to be part of the public API.

Note

The built-in object type is never included as a base class.


Last update: Nov 16, 2024