Repository: sphinx-doc/sphinx

Issue:
Autodoc does not work with enum subclasses with properties/methods
Subject: Autodoc does not work with enum subclasses with properties/methods

<!--
  Important: This is a list of issues for Sphinx, not a forum.
  If you'd like to post a question, please move to sphinx-users group.
  https://groups.google.com/forum/#!forum/sphinx-users

  Thanks,
-->

### Problem
- When a subclass of `enum.Flag` or `enum.IntFlag` has additional methods or properties, those are not displayed by autodoc.

#### Procedure to reproduce the problem

I have this enumeration 

```python
class Maskbit(enum.Flag):
    """A maskbit enumeration. Intended for subclassing."""

    @property
    def active_bits(self):
        """Returns a list of flags that match the value."""

        return [bit for bit in self.__class__ if bit.value & self.value]
```

When documenting it with

```rst
.. automodule:: jaeger.utils.maskbits
    :undoc-members:
```

it shoes the doctoring for the class but not the property or its docstring. The same is true if `active_bits` is a method.

#### Error logs / results
- No errors but the output is not the expected one.

#### Expected results
- Subclasses of enum should be documented as normal classes.

### Reproducible project / your project
- See snippet above. If needed I can provide the full project but shouldn't be necessary.

### Environment info
- OS: macOS 10.14
- Python version: 3.7.0
- Sphinx version: v1.8.0


Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 4289dd325..f657ae9ab 100644
--- a/CHANGES
+++ b/CHANGES
@@ -21,6 +21,7 @@ Bugs fixed
 * #5422: lambda object causes PicklingError on storing environment
 * #5417: Sphinx fails to build with syntax error in Python 2.7.5
 * #4911: add latexpdf to make.bat for non make-mode
+* #5436: Autodoc does not work with enum subclasses with properties/methods
 
 Testing
 --------
diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py
index 041589b58..a2280e82b 100644
--- a/sphinx/ext/autodoc/importer.py
+++ b/sphinx/ext/autodoc/importer.py
@@ -16,7 +16,7 @@
 from collections import namedtuple
 from types import FunctionType, MethodType, ModuleType
 
-from six import PY2
+from six import PY2, iteritems
 
 from sphinx.util import logging
 from sphinx.util.inspect import isenumclass, safe_getattr
@@ -248,6 +248,11 @@ def get_object_members(subject, objpath, attrgetter, analyzer=None):
             if name not in members:
                 members[name] = Attribute(name, True, value)
 
+        superclass = subject.__mro__[1]
+        for name, value in iteritems(obj_dict):
+            if name not in superclass.__dict__:
+                members[name] = Attribute(name, True, value)
+
     # other members
     for name in dir(subject):
         try:


Test patch:
diff --git a/tests/roots/test-ext-autodoc/target/__init__.py b/tests/roots/test-ext-autodoc/target/__init__.py
index 201e84efd..9bb50bca9 100644
--- a/tests/roots/test-ext-autodoc/target/__init__.py
+++ b/tests/roots/test-ext-autodoc/target/__init__.py
@@ -223,19 +223,6 @@ def __init__(self):
         """Docstring for instance attribute InstAttCls.ia2."""
 
 
-class EnumCls(enum.Enum):
-    """
-    this is enum class
-    """
-
-    #: doc for val1
-    val1 = 12
-    val2 = 23  #: doc for val2
-    val3 = 34
-    """doc for val3"""
-    val4 = 34
-
-
 class CustomIter(object):
     def __init__(self):
         """Create a new `CustomIter`."""
diff --git a/tests/roots/test-ext-autodoc/target/enum.py b/tests/roots/test-ext-autodoc/target/enum.py
new file mode 100644
index 000000000..31e7c6ccd
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/enum.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import
+import enum
+
+
+class EnumCls(enum.Enum):
+    """
+    this is enum class
+    """
+
+    #: doc for val1
+    val1 = 12
+    val2 = 23  #: doc for val2
+    val3 = 34
+    """doc for val3"""
+    val4 = 34
+
+    def say_hello(self):
+        """a method says hello to you."""
+        pass
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index e2dc37c56..554ad180f 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -829,7 +829,6 @@ def test_autodoc_ignore_module_all(app):
         '.. py:class:: CustomDataDescriptor2(doc)',
         '.. py:class:: CustomDataDescriptorMeta',
         '.. py:class:: CustomDict',
-        '.. py:class:: EnumCls',
         '.. py:class:: InstAttCls()',
         '.. py:class:: Outer',
         '   .. py:class:: Outer.Inner',
@@ -1263,48 +1262,54 @@ def test_instance_attributes(app):
 def test_enum_class(app):
     options = {"members": None,
                "undoc-members": True}
-    actual = do_autodoc(app, 'class', 'target.EnumCls', options)
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
     assert list(actual) == [
         '',
         '.. py:class:: EnumCls',
-        '   :module: target',
+        '   :module: target.enum',
         '',
         '   this is enum class',
         '   ',
         '   ',
+        '   .. py:method:: EnumCls.say_hello()',
+        '      :module: target.enum',
+        '   ',
+        '      a method says hello to you.',
+        '      ',
+        '   ',
         '   .. py:attribute:: EnumCls.val1',
-        '      :module: target',
+        '      :module: target.enum',
         '      :annotation: = 12',
         '   ',
         '      doc for val1',
         '      ',
         '   ',
         '   .. py:attribute:: EnumCls.val2',
-        '      :module: target',
+        '      :module: target.enum',
         '      :annotation: = 23',
         '   ',
         '      doc for val2',
         '      ',
         '   ',
         '   .. py:attribute:: EnumCls.val3',
-        '      :module: target',
+        '      :module: target.enum',
         '      :annotation: = 34',
         '   ',
         '      doc for val3',
         '      ',
         '   ',
         '   .. py:attribute:: EnumCls.val4',
-        '      :module: target',
+        '      :module: target.enum',
         '      :annotation: = 34',
         '   '
     ]
 
     # checks for an attribute of EnumClass
-    actual = do_autodoc(app, 'attribute', 'target.EnumCls.val1')
+    actual = do_autodoc(app, 'attribute', 'target.enum.EnumCls.val1')
     assert list(actual) == [
         '',
         '.. py:attribute:: EnumCls.val1',
-        '   :module: target',
+        '   :module: target.enum',
         '   :annotation: = 12',
         '',
         '   doc for val1',
@@ -1473,7 +1478,7 @@ def test_merge_autodoc_default_flags2(app):
 @pytest.mark.sphinx('html', testroot='ext-autodoc')
 def test_autodoc_default_options(app):
     # no settings
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' not in actual
     assert '   .. py:attribute:: EnumCls.val4' not in actual
     actual = do_autodoc(app, 'class', 'target.CustomIter')
@@ -1481,7 +1486,7 @@ def test_autodoc_default_options(app):
 
     # with :members:
     app.config.autodoc_default_options = {'members': None}
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' in actual
     assert '   .. py:attribute:: EnumCls.val4' not in actual
 
@@ -1490,7 +1495,7 @@ def test_autodoc_default_options(app):
         'members': None,
         'undoc-members': None,
     }
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' in actual
     assert '   .. py:attribute:: EnumCls.val4' in actual
 
@@ -1516,7 +1521,7 @@ def test_autodoc_default_options(app):
         'members': None,
         'exclude-members': None,
     }
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' in actual
     assert '   .. py:attribute:: EnumCls.val4' not in actual
     app.config.autodoc_default_options = {
@@ -1540,7 +1545,7 @@ def test_autodoc_default_options(app):
 def test_autodoc_default_options_with_values(app):
     # with :members:
     app.config.autodoc_default_options = {'members': 'val1,val2'}
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' in actual
     assert '   .. py:attribute:: EnumCls.val2' in actual
     assert '   .. py:attribute:: EnumCls.val3' not in actual
@@ -1564,7 +1569,7 @@ def test_autodoc_default_options_with_values(app):
         'members': None,
         'exclude-members': 'val1'
     }
-    actual = do_autodoc(app, 'class', 'target.EnumCls')
+    actual = do_autodoc(app, 'class', 'target.enum.EnumCls')
     assert '   .. py:attribute:: EnumCls.val1' not in actual
     assert '   .. py:attribute:: EnumCls.val2' in actual
     assert '   .. py:attribute:: EnumCls.val3' in actual