[autodoc] fix rendering of enumerations inheriting from mixin and data types (#11596)

This commit is contained in:
Bénédikt Tran
2024-03-21 12:36:36 +01:00
committed by GitHub
parent 078a80a42d
commit da0733b338
4 changed files with 736 additions and 104 deletions

View File

@@ -103,6 +103,8 @@ Bugs fixed
* #11925: Blacklist the ``sphinxprettysearchresults`` extension; the functionality
it provides was merged into Sphinx v2.0.0.
Patch by James Addison.
* #11353: Support enumeration classes inheriting from mixin or data types.
Patch by Bénédikt Tran.
* #11962: Fix target resolution when using ``:paramtype:`` fields.
Patch by Bénédikt Tran.
* #12008: Fix case-sensitive lookup of ``std:label`` names in intersphinx inventory.

View File

@@ -8,7 +8,8 @@ import os
import sys
import traceback
import typing
from typing import TYPE_CHECKING, Any, Callable, NamedTuple
from enum import Enum
from typing import TYPE_CHECKING, NamedTuple
from sphinx.ext.autodoc.mock import ismock, undecorate
from sphinx.pycode import ModuleAnalyzer, PycodeError
@@ -20,16 +21,91 @@ from sphinx.util.inspect import (
isclass,
isenumclass,
safe_getattr,
unwrap_all,
)
if TYPE_CHECKING:
from collections.abc import Callable, Generator, Mapping
from types import ModuleType
from typing import Any
from sphinx.ext.autodoc import ObjectMember
logger = logging.getLogger(__name__)
def _filter_enum_dict(
enum_class: type[Enum],
attrgetter: Callable[[Any, str, Any], Any],
enum_class_dict: Mapping[str, object],
) -> Generator[tuple[str, type, Any], None, None]:
"""Find the attributes to document of an enumeration class.
The output consists of triplets ``(attribute name, defining class, value)``
where the attribute name can appear more than once during the iteration
but with different defining class. The order of occurrence is guided by
the MRO of *enum_class*.
"""
# attributes that were found on a mixin type or the data type
candidate_in_mro: set[str] = set()
# sunder names that were picked up (and thereby allowed to be redefined)
# see: https://docs.python.org/3/howto/enum.html#supported-dunder-names
sunder_names = {'_name_', '_value_', '_missing_', '_order_', '_generate_next_value_'}
# attributes that can be picked up on a mixin type or the enum's data type
public_names = {'name', 'value', *object.__dict__, *sunder_names}
# names that are ignored by default
ignore_names = Enum.__dict__.keys() - public_names
def is_native_api(obj: object, name: str) -> bool:
"""Check whether *obj* is the same as ``Enum.__dict__[name]``."""
return unwrap_all(obj) is unwrap_all(Enum.__dict__[name])
def should_ignore(name: str, value: Any) -> bool:
if name in sunder_names:
return is_native_api(value, name)
return name in ignore_names
sentinel = object()
def query(name: str, defining_class: type) -> tuple[str, type, Any] | None:
value = attrgetter(enum_class, name, sentinel)
if value is not sentinel:
return (name, defining_class, value)
return None
# attributes defined on a parent type, possibly shadowed later by
# the attributes defined directly inside the enumeration class
for parent in enum_class.__mro__:
if parent in {enum_class, Enum, object}:
continue
parent_dict = attrgetter(parent, '__dict__', {})
for name, value in parent_dict.items():
if should_ignore(name, value):
continue
candidate_in_mro.add(name)
if (item := query(name, parent)) is not None:
yield item
# exclude members coming from the native Enum unless
# they were redefined on a mixin type or the data type
excluded_members = Enum.__dict__.keys() - candidate_in_mro
yield from filter(None, (query(name, enum_class) for name in enum_class_dict
if name not in excluded_members))
# check if allowed members from ``Enum`` were redefined at the enum level
special_names = sunder_names | public_names
special_names &= enum_class_dict.keys()
special_names &= Enum.__dict__.keys()
for name in special_names:
if (
not is_native_api(enum_class_dict[name], name)
and (item := query(name, enum_class)) is not None
):
yield item
def mangle(subject: Any, name: str) -> str:
"""Mangle the given name."""
try:
@@ -192,15 +268,11 @@ def get_object_members(
# enum members
if isenumclass(subject):
for name, value in subject.__members__.items():
if name not in members:
members[name] = Attribute(name, True, value)
superclass = subject.__mro__[1]
for name in obj_dict:
if name not in superclass.__dict__:
value = safe_getattr(subject, name)
members[name] = Attribute(name, True, value)
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
# the order of occurrence of *name* matches the subject's MRO,
# allowing inherited attributes to be shadowed correctly
if unmangled := unmangle(defining_class, name):
members[unmangled] = Attribute(unmangled, defining_class is subject, value)
# members in __slots__
try:
@@ -218,18 +290,18 @@ def get_object_members(
try:
value = attrgetter(subject, name)
directly_defined = name in obj_dict
name = unmangle(subject, name)
if name and name not in members:
members[name] = Attribute(name, directly_defined, value)
unmangled = unmangle(subject, name)
if unmangled and unmangled not in members:
members[unmangled] = Attribute(unmangled, directly_defined, value)
except AttributeError:
continue
# annotation only member (ex. attr: int)
for i, cls in enumerate(getmro(subject)):
for cls in getmro(subject):
for name in getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = Attribute(name, i == 0, INSTANCEATTR)
unmangled = unmangle(cls, name)
if unmangled and unmangled not in members:
members[unmangled] = Attribute(unmangled, cls is subject, INSTANCEATTR)
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows
@@ -253,15 +325,11 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
# enum members
if isenumclass(subject):
for name, value in subject.__members__.items():
if name not in members:
members[name] = ObjectMember(name, value, class_=subject)
superclass = subject.__mro__[1]
for name in obj_dict:
if name not in superclass.__dict__:
value = safe_getattr(subject, name)
members[name] = ObjectMember(name, value, class_=subject)
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
# the order of occurrence of *name* matches the subject's MRO,
# allowing inherited attributes to be shadowed correctly
if unmangled := unmangle(defining_class, name):
members[unmangled] = ObjectMember(unmangled, value, class_=defining_class)
# members in __slots__
try:
@@ -306,15 +374,15 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
# annotation only member (ex. attr: int)
for name in getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
if analyzer and (qualname, name) in analyzer.attr_docs:
docstring = '\n'.join(analyzer.attr_docs[qualname, name])
unmangled = unmangle(cls, name)
if unmangled and unmangled not in members:
if analyzer and (qualname, unmangled) in analyzer.attr_docs:
docstring = '\n'.join(analyzer.attr_docs[qualname, unmangled])
else:
docstring = None
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
docstring=docstring)
members[unmangled] = ObjectMember(unmangled, INSTANCEATTR, class_=cls,
docstring=docstring)
# append or complete instance attributes (cf. self.attr1) if analyzer knows
if analyzer:

View File

@@ -1,10 +1,46 @@
# ruff: NoQA: D403, PIE796
import enum
from typing import final
class MemberType:
"""Custom data type with a simple API."""
# this mangled attribute will never be shown on subclasses
# even if :inherited-members: and :private-members: are set
__slots__ = ('__data',)
def __new__(cls, value):
self = object.__new__(cls)
self.__data = value
return self
def __str__(self):
"""inherited"""
return self.__data
def __repr__(self):
return repr(self.__data)
def __reduce__(self):
# data types must be pickable, otherwise enum classes using this data
# type will be forced to be non-pickable and have their __module__ set
# to '<unknown>' instead of, for instance, '__main__'
return self.__class__, (self.__data,)
@final
@property
def dtype(self):
"""docstring"""
return 'str'
def isupper(self):
"""inherited"""
return self.__data.isupper()
class EnumCls(enum.Enum):
"""
this is enum class
"""
"""this is enum class"""
#: doc for val1
val1 = 12
@@ -15,9 +51,194 @@ class EnumCls(enum.Enum):
def say_hello(self):
"""a method says hello to you."""
pass
@classmethod
def say_goodbye(cls):
"""a classmethod says good-bye to you."""
pass
class EnumClassWithDataType(MemberType, enum.Enum):
"""this is enum class"""
x = 'x'
def say_hello(self):
"""docstring"""
@classmethod
def say_goodbye(cls):
"""docstring"""
class ToUpperCase: # not inheriting from enum.Enum
@property
def value(self): # bypass enum.Enum.value
"""uppercased"""
return str(self._value_).upper() # type: ignore[attr-defined]
class Greeter:
def say_hello(self):
"""inherited"""
@classmethod
def say_goodbye(cls):
"""inherited"""
class EnumClassWithMixinType(ToUpperCase, enum.Enum):
"""this is enum class"""
x = 'x'
def say_hello(self):
"""docstring"""
@classmethod
def say_goodbye(cls):
"""docstring"""
class EnumClassWithMixinTypeInherit(Greeter, ToUpperCase, enum.Enum):
"""this is enum class"""
x = 'x'
class Overridden(enum.Enum):
def override(self):
"""inherited"""
return 1
class EnumClassWithMixinEnumType(Greeter, Overridden, enum.Enum):
"""this is enum class"""
x = 'x'
def override(self):
"""overridden"""
return 2
class EnumClassWithMixinAndDataType(Greeter, ToUpperCase, MemberType, enum.Enum):
"""this is enum class"""
x = 'x'
def say_hello(self):
"""overridden"""
@classmethod
def say_goodbye(cls):
"""overridden"""
def isupper(self):
"""overridden"""
return False
def __str__(self):
"""overridden"""
return super().__str__()
class _ParentEnum(Greeter, Overridden, enum.Enum):
"""docstring"""
class EnumClassWithParentEnum(ToUpperCase, MemberType, _ParentEnum, enum.Enum):
"""this is enum class"""
x = 'x'
def isupper(self):
"""overridden"""
return False
def __str__(self):
"""overridden"""
return super().__str__()
class _SunderMissingInNonEnumMixin:
@classmethod
def _missing_(cls, value):
"""inherited"""
return super()._missing_(value) # type: ignore[misc]
class _SunderMissingInEnumMixin(enum.Enum):
@classmethod
def _missing_(cls, value):
"""inherited"""
return super()._missing_(value)
class _SunderMissingInDataType(MemberType):
@classmethod
def _missing_(cls, value):
"""inherited"""
return super()._missing_(value) # type: ignore[misc]
class EnumSunderMissingInNonEnumMixin(_SunderMissingInNonEnumMixin, enum.Enum):
"""this is enum class"""
class EnumSunderMissingInEnumMixin(_SunderMissingInEnumMixin, enum.Enum):
"""this is enum class"""
class EnumSunderMissingInDataType(_SunderMissingInDataType, enum.Enum):
"""this is enum class"""
class EnumSunderMissingInClass(enum.Enum):
"""this is enum class"""
@classmethod
def _missing_(cls, value):
"""docstring"""
return super()._missing_(value)
class _NamePropertyInNonEnumMixin:
@property
def name(self):
"""inherited"""
return super().name # type: ignore[misc]
class _NamePropertyInEnumMixin(enum.Enum):
@property
def name(self):
"""inherited"""
return super().name
class _NamePropertyInDataType(MemberType):
@property
def name(self):
"""inherited"""
return super().name # type: ignore[misc]
class EnumNamePropertyInNonEnumMixin(_NamePropertyInNonEnumMixin, enum.Enum):
"""this is enum class"""
class EnumNamePropertyInEnumMixin(_NamePropertyInEnumMixin, enum.Enum):
"""this is enum class"""
class EnumNamePropertyInDataType(_NamePropertyInDataType, enum.Enum):
"""this is enum class"""
class EnumNamePropertyInClass(enum.Enum):
"""this is enum class"""
@property
def name(self):
"""docstring"""
return super().name

View File

@@ -4,10 +4,14 @@ This tests mainly the Documenters; the auto directives are tested in a test
source file translated by test_build.
"""
from __future__ import annotations
import functools
import itertools
import operator
import sys
from types import SimpleNamespace
from typing import TYPE_CHECKING
from unittest.mock import Mock
from warnings import catch_warnings
@@ -26,10 +30,12 @@ try:
except ImportError:
pyximport = None
if TYPE_CHECKING:
from typing import Any
def do_autodoc(app, objtype, name, options=None):
if options is None:
options = {}
options = {} if options is None else options.copy()
app.env.temp_data.setdefault('docname', 'index') # set dummy docname
doccls = app.registry.documenters[objtype]
docoptions = process_documenter_options(doccls, app.config, options)
@@ -105,7 +111,7 @@ def test_parse_name(app):
verify('module', 'test_ext_autodoc', ('test_ext_autodoc', [], None, None))
verify('module', 'test.test_ext_autodoc', ('test.test_ext_autodoc', [], None, None))
verify('module', 'test(arg)', ('test', [], 'arg', None))
assert 'signature arguments' in app._warning.getvalue()
assert 'signature arguments' in app.warning.getvalue()
# for functions/classes
verify('function', 'test_ext_autodoc.raises',
@@ -1407,76 +1413,411 @@ def test_slots(app):
]
class _EnumFormatter:
def __init__(self, name: str, *, module: str = 'target.enums') -> None:
self.name = name
self.module = module
@property
def target(self) -> str:
"""The autodoc target class."""
return f'{self.module}.{self.name}'
def subtarget(self, name: str) -> str:
"""The autodoc sub-target (an attribute, method, etc)."""
return f'{self.target}.{name}'
def _node(
self, role: str, name: str, doc: str, *, args: str, indent: int, **options: Any,
) -> list[str]:
prefix = indent * ' '
tab = ' ' * 3
def rst_option(name: str, value: Any) -> str:
value = '' if value in {1, True} else value
return f'{prefix}{tab}:{name}: {value!s}'.rstrip()
lines = [
'',
f'{prefix}.. py:{role}:: {name}{args}',
f'{prefix}{tab}:module: {self.module}',
*itertools.starmap(rst_option, options.items()),
]
if doc:
lines.extend(['', f'{prefix}{tab}{doc}'])
lines.append('')
return lines
def entry(
self,
entry_name: str,
doc: str = '',
*,
role: str,
args: str = '',
indent: int = 3,
**rst_options: Any,
) -> list[str]:
"""Get the RST lines for a named attribute, method, etc."""
qualname = f'{self.name}.{entry_name}'
return self._node(role, qualname, doc, args=args, indent=indent, **rst_options)
def brief(self, doc: str, *, indent: int = 0, **options: Any) -> list[str]:
"""Generate the brief part of the class being documented."""
assert doc, f'enumeration class {self.target!r} should have an explicit docstring'
if sys.version_info[:2] >= (3, 13):
args = ('(value, names=<not given>, *values, module=None, '
'qualname=None, type=None, start=1, boundary=None)')
elif sys.version_info[:2] >= (3, 12):
args = ('(value, names=None, *values, module=None, '
'qualname=None, type=None, start=1, boundary=None)')
elif sys.version_info[:2] >= (3, 11):
args = ('(value, names=None, *, module=None, qualname=None, '
'type=None, start=1, boundary=None)')
else:
args = '(value)'
return self._node('class', self.name, doc, args=args, indent=indent, **options)
def method(
self, name: str, doc: str, *flags: str, args: str = '()', indent: int = 3,
) -> list[str]:
rst_options = dict.fromkeys(flags, '')
return self.entry(name, doc, role='method', args=args, indent=indent, **rst_options)
def member(self, name: str, value: Any, doc: str, *, indent: int = 3) -> list[str]:
rst_options = {'value': repr(value)}
return self.entry(name, doc, role='attribute', indent=indent, **rst_options)
@pytest.fixture()
def autodoc_enum_options() -> dict[str, object]:
"""Default autodoc options to use when testing enum's documentation."""
return {"members": None, "undoc-members": None}
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app):
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)
if sys.version_info[:2] >= (3, 13):
args = ('(value, names=<not given>, *values, module=None, '
'qualname=None, type=None, start=1, boundary=None)')
elif sys.version_info[:2] >= (3, 12):
args = ('(value, names=None, *values, module=None, '
'qualname=None, type=None, start=1, boundary=None)')
elif sys.version_info[:2] >= (3, 11):
args = ('(value, names=None, *, module=None, qualname=None, '
'type=None, start=1, boundary=None)')
else:
args = '(value)'
def test_enum_class(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumCls')
options = autodoc_enum_options | {'private-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
'',
'.. py:class:: EnumCls' + args,
' :module: target.enums',
'',
' this is enum class',
'',
'',
' .. py:method:: EnumCls.say_goodbye()',
' :module: target.enums',
' :classmethod:',
'',
' a classmethod says good-bye to you.',
'',
'',
' .. py:method:: EnumCls.say_hello()',
' :module: target.enums',
'',
' a method says hello to you.',
'',
'',
' .. py:attribute:: EnumCls.val1',
' :module: target.enums',
' :value: 12',
'',
' doc for val1',
'',
'',
' .. py:attribute:: EnumCls.val2',
' :module: target.enums',
' :value: 23',
'',
' doc for val2',
'',
'',
' .. py:attribute:: EnumCls.val3',
' :module: target.enums',
' :value: 34',
'',
' doc for val3',
'',
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'a classmethod says good-bye to you.', 'classmethod'),
*fmt.method('say_hello', 'a method says hello to you.'),
*fmt.member('val1', 12, 'doc for val1'),
*fmt.member('val2', 23, 'doc for val2'),
*fmt.member('val3', 34, 'doc for val3'),
*fmt.member('val4', 34, ''), # val4 is alias of val3
]
# checks for an attribute of EnumClass
actual = do_autodoc(app, 'attribute', 'target.enums.EnumCls.val1')
# Inherited members exclude the native Enum API (in particular
# the 'name' and 'value' properties), unless they were explicitly
# redefined by the user in one of the bases.
actual = do_autodoc(app, 'class', fmt.target, options | {'inherited-members': None})
assert list(actual) == [
'',
'.. py:attribute:: EnumCls.val1',
' :module: target.enums',
' :value: 12',
'',
' doc for val1',
'',
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'a classmethod says good-bye to you.', 'classmethod'),
*fmt.method('say_hello', 'a method says hello to you.'),
*fmt.member('val1', 12, 'doc for val1'),
*fmt.member('val2', 23, 'doc for val2'),
*fmt.member('val3', 34, 'doc for val3'),
*fmt.member('val4', 34, ''), # val4 is alias of val3
]
# checks for an attribute of EnumCls
actual = do_autodoc(app, 'attribute', fmt.subtarget('val1'))
assert list(actual) == fmt.member('val1', 12, 'doc for val1', indent=0)
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class_with_data_type(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithDataType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'docstring', 'classmethod'),
*fmt.method('say_hello', 'docstring'),
*fmt.member('x', 'x', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('dtype', 'docstring', role='property'),
*fmt.method('isupper', 'inherited'),
*fmt.method('say_goodbye', 'docstring', 'classmethod'),
*fmt.method('say_hello', 'docstring'),
*fmt.member('x', 'x', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class_with_mixin_type(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithMixinType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'docstring', 'classmethod'),
*fmt.method('say_hello', 'docstring'),
*fmt.member('x', 'X', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'docstring', 'classmethod'),
*fmt.method('say_hello', 'docstring'),
*fmt.entry('value', 'uppercased', role='property'),
*fmt.member('x', 'X', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class_with_mixin_type_and_inheritence(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithMixinTypeInherit')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.member('x', 'X', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('say_goodbye', 'inherited', 'classmethod'),
*fmt.method('say_hello', 'inherited'),
*fmt.entry('value', 'uppercased', role='property'),
*fmt.member('x', 'X', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class_with_mixin_enum_type(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithMixinEnumType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
# override() is overridden at the class level so it should be rendered
*fmt.method('override', 'overridden'),
# say_goodbye() and say_hello() are not rendered since they are inherited
*fmt.member('x', 'x', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('override', 'overridden'),
*fmt.method('say_goodbye', 'inherited', 'classmethod'),
*fmt.method('say_hello', 'inherited'),
*fmt.member('x', 'x', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class_with_mixin_and_data_type(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithMixinAndDataType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('isupper', 'overridden'),
*fmt.method('say_goodbye', 'overridden', 'classmethod'),
*fmt.method('say_hello', 'overridden'),
*fmt.member('x', 'X', ''),
]
# add the special member __str__ (but not the inherited members)
options = autodoc_enum_options | {'special-members': '__str__'}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('__str__', 'overridden'),
*fmt.method('isupper', 'overridden'),
*fmt.method('say_goodbye', 'overridden', 'classmethod'),
*fmt.method('say_hello', 'overridden'),
*fmt.member('x', 'X', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('dtype', 'docstring', role='property'),
*fmt.method('isupper', 'overridden'),
*fmt.method('say_goodbye', 'overridden', 'classmethod'),
*fmt.method('say_hello', 'overridden'),
*fmt.entry('value', 'uppercased', role='property'),
*fmt.member('x', 'X', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_with_parent_enum(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumClassWithParentEnum')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('isupper', 'overridden'),
*fmt.member('x', 'X', ''),
]
# add the special member __str__ (but not the inherited members)
options = autodoc_enum_options | {'special-members': '__str__'}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('__str__', 'overridden'),
*fmt.method('isupper', 'overridden'),
*fmt.member('x', 'X', ''),
]
options = autodoc_enum_options | {'inherited-members': None}
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('dtype', 'docstring', role='property'),
*fmt.method('isupper', 'overridden'),
*fmt.method('override', 'inherited'),
*fmt.method('say_goodbye', 'inherited', 'classmethod'),
*fmt.method('say_hello', 'inherited'),
*fmt.entry('value', 'uppercased', role='property'),
*fmt.member('x', 'X', ''),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_sunder_method(app, autodoc_enum_options):
PRIVATE = {'private-members': None} # sunder methods are recognized as private
fmt = _EnumFormatter('EnumSunderMissingInNonEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options | PRIVATE)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumSunderMissingInEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options | PRIVATE)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumSunderMissingInDataType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options | PRIVATE)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumSunderMissingInClass')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options | PRIVATE)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('_missing_', 'docstring', 'classmethod', args='(value)'),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_inherited_sunder_method(app, autodoc_enum_options):
options = autodoc_enum_options | {'private-members': None, 'inherited-members': None}
fmt = _EnumFormatter('EnumSunderMissingInNonEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('_missing_', 'inherited', 'classmethod', args='(value)'),
]
fmt = _EnumFormatter('EnumSunderMissingInEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('_missing_', 'inherited', 'classmethod', args='(value)'),
]
fmt = _EnumFormatter('EnumSunderMissingInDataType')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('_missing_', 'inherited', 'classmethod', args='(value)'),
*fmt.entry('dtype', 'docstring', role='property'),
*fmt.method('isupper', 'inherited'),
]
fmt = _EnumFormatter('EnumSunderMissingInClass')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.method('_missing_', 'docstring', 'classmethod', args='(value)'),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_custom_name_property(app, autodoc_enum_options):
fmt = _EnumFormatter('EnumNamePropertyInNonEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumNamePropertyInEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumNamePropertyInDataType')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [*fmt.brief('this is enum class')]
fmt = _EnumFormatter('EnumNamePropertyInClass')
actual = do_autodoc(app, 'class', fmt.target, autodoc_enum_options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('name', 'docstring', role='property'),
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_inherited_custom_name_property(app, autodoc_enum_options):
options = autodoc_enum_options | {"inherited-members": None}
fmt = _EnumFormatter('EnumNamePropertyInNonEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('name', 'inherited', role='property'),
]
fmt = _EnumFormatter('EnumNamePropertyInEnumMixin')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('name', 'inherited', role='property'),
]
fmt = _EnumFormatter('EnumNamePropertyInDataType')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('dtype', 'docstring', role='property'),
*fmt.method('isupper', 'inherited'),
*fmt.entry('name', 'inherited', role='property'),
]
fmt = _EnumFormatter('EnumNamePropertyInClass')
actual = do_autodoc(app, 'class', fmt.target, options)
assert list(actual) == [
*fmt.brief('this is enum class'),
*fmt.entry('name', 'docstring', role='property'),
]