Sphinx

# Developing autodoc extensions

The objective of this tutorial is to create an extension that adds support for new type for autodoc. This autodoc extension will format the `IntEnum` class from Python standard library. (module `enum`)

## Overview

We want the extension that will create auto-documentation for IntEnum. `IntEnum` is the integer enum class from standard library `enum` module.

Currently this class has no special auto documentation behavior.

We want to add following to autodoc:

- A new `autointenum` directive that will document the `IntEnum` class.

- The generated documentation will have all the enum possible values with names.

- The `autointenum` directive will have an option `:hex:` which will cause the integers be printed in hexadecimal form.

## Prerequisites

We need the same setup as in the previous extensions. This time, we will be putting out extension in a file called `autodoc_intenum.py`. The `my_enums.py` will contain the sample enums we will document.

Here is an example of the folder structure you might obtain:

    └── source
        ├── _ext
        │   └── autodoc_intenum.py
        ├── conf.py
        ├── index.rst
        └── my_enums.py

## Writing the extension

Start with `setup` function for the extension.

    1def setup(app: Sphinx) -> ExtensionMetadata:
    2    app.setup_extension('sphinx.ext.autodoc')  # Require autodoc extension
    3    app.add_autodocumenter(IntEnumDocumenter)
    4    return {
    5        'version': '1',
    6        'parallel_read_safe': True,
    7    }

The `setup_extension()` method will pull the autodoc extension because our new extension depends on autodoc. `add_autodocumenter()` is the method that registers our new auto documenter class.

We want to import certain objects from the autodoc extension:

    1from __future__ import annotations
    2
    3from enum import IntEnum
    4from typing import TYPE_CHECKING
    5
    6from sphinx.ext.autodoc import ClassDocumenter, bool_option
    7

There are several different documenter classes such as `MethodDocumenter` or `AttributeDocumenter` available in the autodoc extension but our new class is the subclass of `ClassDocumenter` which a documenter class used by autodoc to document classes.

This is the definition of our new the auto-documenter class:

     1class IntEnumDocumenter(ClassDocumenter):
     2    objtype = 'intenum'
     3    directivetype = ClassDocumenter.objtype
     4    priority = 10 + ClassDocumenter.priority
     5    option_spec = dict(ClassDocumenter.option_spec)
     6    option_spec['hex'] = bool_option
     7
     8    @classmethod
     9    def can_document_member(
    10        cls, member: Any, membername: str, isattr: bool, parent: Any
    11    ) -> bool:
    12        try:
    13            return issubclass(member, IntEnum)
    14        except TypeError:
    15            return False
    16
    17    def add_directive_header(self, sig: str) -> None:
    18        super().add_directive_header(sig)
    19        self.add_line('   :final:', self.get_sourcename())
    20
    21    def add_content(
    22        self,
    23        more_content: StringList | None,
    24    ) -> None:
    25        super().add_content(more_content)
    26
    27        source_name = self.get_sourcename()
    28        enum_object: IntEnum = self.object
    29        use_hex = self.options.hex
    30        self.add_line('', source_name)
    31
    32        for the_member_name, enum_member in enum_object.__members__.items():  # type: ignore[attr-defined]
    33            the_member_value = enum_member.value
    34            if use_hex:
    35                the_member_value = hex(the_member_value)
    36
    37            self.add_line(f'**{the_member_name}**: {the_member_value}', source_name)
    38            self.add_line('', source_name)

Important attributes of the new class:

objtype

  This attribute determines the `auto` directive name. In this case the auto directive will be `autointenum`.

directivetype

  This attribute sets the generated directive name. In this example the generated directive will be `.. :py:class::`.

priority

  the larger the number the higher is the priority. We want our documenter be higher priority than the parent.

option_spec

  option specifications. We copy the parent class options and add a new option hex.

Overridden members:

can_document_member

  This member is important to override. It should return True when the passed object can be documented by this class.

add_directive_header

  This method generates the directive header. We add :final: directive option. Remember to call super or no directive will be generated.

add_content

  This method generates the body of the class documentation. After calling the super method we generate lines for enum description.

## Using the extension

You can now use the new autodoc directive to document any `IntEnum`.

For example, you have the following `IntEnum`:

my_enums.py

    class Colors(IntEnum):
        """Colors enumerator"""
        NONE = 0
        RED = 1
        GREEN = 2
        BLUE = 3

This will be the documentation file with auto-documentation directive:

index.rst

    .. autointenum:: my_enums.Colors

## Further reading

If you wish to share your extension across multiple projects or with others, check out the Third-party extensions section.