Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sphinx not documenting complex Enum classes

In my code I have some classes that are complex Enum types. For example:

class ComplexEnum(SomeOtherClass, Enum):
    """ Some documentation """

    MEMBER1 = SomeOtherClass(1)
    MEMBER2 = SomeOtherClass(2)

    def __init__(self, arg):
        """ more doc """
        pass

    def somemethod(self):
        """ more doc """
        pass

    @classmethod
    def someclassmethod(cls, otherparam):
        """ more doc """
        pass

When I now create my documentation with Sphinx using autodoc this class is just skipped. I tried adding a custom documenter like this to my conf.py file:

from sphinx.ext.autodoc import ClassDocumenter

class MyClassDocumenter(ClassDocumenter):
    objtype = 'ComplexEnum'
    directivetype = 'class'

    @classmethod
    def can_document_member(cls, member, membername, isattr, parent):
        return isinstance(member, ComplexEnum)

def setup(app):
    app.add_autodocumenter(MyClassDocumenter)

But this does not work either.

How can I make sphinx document those kind of classes?

like image 526
Rittel Avatar asked Jan 06 '20 11:01

Rittel


1 Answers

This is a bug in Sphinx autodoc occurring with some uses of Enum.

It can be solved by careful workarounds writing the .rst files.

Having said that, I assume this is aimed for:

enter image description here

The corresponding .rst:

my_module module
================

.. automodule:: my_module
   :exclude-members: ComplexEnum


   .. autoclass:: ComplexEnum
      :members: some_method
      :show-inheritance:
      :exclude-members: MEMBER1, MEMBER2, __init__, some_classmethod

      .. automethod:: some_classmethod  

      .. autoattribute:: MEMBER1
         :annotation: = SomeOtherClass(1)

      .. autoattribute:: MEMBER2
         :annotation: = SomeOtherClass(2)

      .. automethod:: __init__

   .. autoclass:: SomeOtherClass
      :special-members: __init__

I slightly altered the code to better explain some details of the workarounds:

from enum import Enum


class SomeOtherClass:
    """ SomeOtherClass documentation """

    def __init__(self, other_arg):
        """Example of docstring on the __init__ method.

        Args:
            other_arg (int): Description of `other_arg`.
        """
        self.other_arg = other_arg


class ComplexEnum(SomeOtherClass, Enum):
    """ComplexEnum documentation."""

    #: :py:mod:`~my_package.my_module.SomeOtherClass`: MEMBER1 docstring comment.
    MEMBER1 = SomeOtherClass(1)
    #: :py:mod:`~my_package.my_module.SomeOtherClass`: MEMBER2 docstring comment.
    MEMBER2 = SomeOtherClass(2)

    def __init__(self, complex_arg):
        """Example of docstring on the __init__ method.

        Args:
            complex_arg (int): Description of `complex_arg`.
        """
        self.complex_arg = complex_arg
        super().__init__(complex_arg)

    def some_method(self):
        """The doc of some_method."""
        pass

    @classmethod
    def some_classmethod(cls, some_arg):
        """The doc of some_classmethod.

        Args:
            some_arg (int): Description of `some_arg`.
        """
        pass

Your conf.py can be left standard, I only added extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] to enable google style comments.



The specific combination of conditions triggering the bug are identified, so far, in the link @mzjn contributed, and by your post, namely:

  1. Using @classmethod + IntEnum.
  2. Using @classmethod + multiple inheritance, one of the parents being Enum.

It should be noted: using a simple Enum with @classmethod does not trigger the bug. (In that case .. autoclass:: behaves as expected and takes care of almost everything.)



The bug affects several autodoc directives and their options, causing them to have unexpected behavior.

The necessary workarounds in writing the .rst are as follows:

  1. DO NOT use :undoc-members: in the Enum, or else caos breaks out. If you do, the @classmethod with always be included without picking up the descriptor or the docstring, and excluding it with :exclude-members: will have no effect.

  2. Next __init__ is the most problematic aspect. What worked was excluding it with :exclude-members:, together with explicitly using .. automethod:: __init__.

  3. Together with the above: you CAN NOT put the @classmethod next to __init__ using :automethod: in the .rst, or else the entire @classmethod gets "absorved" as part of the __init__ docstring.

  4. What worked best, for me, is including/excluding all parts of the Enum explicitly with :members: and :exclude-members:. That guarantees the best consistency to the behavior of the autodoc directives/options.



Two last notes are pertinent to documenting Enum's using Sphinx (not directly related to the bug).

  1. When documenting Enum members, for best consistency use #: syntax instead of triple-quotes ''' or inline #. The reason is, because the later are frequently "mixed-up" or even lost by Sphinx.

    • The above is usually so even if using ..member-order: by source as a directive option or in the configurations.
  2. Finally, in case you want the values of the Enum members to show in the documentation, as they appear in the class declaration syntax. The best way, in my experience, is using an :annotation: as shown in the .rst. Or else, the Enum members will show in documentation like this:

enter image description here

Using Python 3.8 with Sphinx v2.2.2.

like image 174
bad_coder Avatar answered Nov 12 '22 00:11

bad_coder