diff --git a/sphinx_automodapi/autodoc_enhancements.py b/sphinx_automodapi/autodoc_enhancements.py index 7721043..84ec880 100644 --- a/sphinx_automodapi/autodoc_enhancements.py +++ b/sphinx_automodapi/autodoc_enhancements.py @@ -1,6 +1,8 @@ """ Miscellaneous enhancements to help autodoc along. """ +import dataclasses + from sphinx.ext.autodoc import AttributeDocumenter __all__ = [] @@ -58,7 +60,19 @@ def type_object_attrgetter(obj, attr, *defargs): return base.__dict__[attr] break - return getattr(obj, attr, *defargs) + try: + return getattr(obj, attr, *defargs) + except AttributeError as e: + # for dataclasses, get the attribute from the __dataclass_fields__ + if dataclasses.is_dataclass(obj): + if attr in obj.__dataclass_fields__: + return obj.__dataclass_fields__[attr].name + else: + # raise original AttributeError + raise e + else: + # raise original AttributeError + raise e def setup(app): diff --git a/sphinx_automodapi/automodsumm.py b/sphinx_automodapi/automodsumm.py index 10a71f6..2d8b280 100644 --- a/sphinx_automodapi/automodsumm.py +++ b/sphinx_automodapi/automodsumm.py @@ -87,6 +87,7 @@ class members that are inherited from a base class. This value can be import inspect import os import re +import dataclasses from sphinx.util import logging from sphinx.ext.autosummary import Autosummary @@ -548,11 +549,22 @@ def get_members_class(obj, typ, include_public=[], else: names = getattr(obj, '__dict__').keys() + # add dataclass_field names for dataclass classes + if dataclasses.is_dataclass(obj): + dataclass_fieldnames = getattr(obj, '__dataclass_fields__').keys() + names = list(set(list(names) + list(dataclass_fieldnames))) + for name in names: try: documenter = get_documenter(app, safe_getattr(obj, name), obj) except AttributeError: - continue + # for dataclasses try to get the attribute from the __dataclass_fields__ + if dataclasses.is_dataclass(obj): + try: + attr = obj.__dataclass_fields__[name] + documenter = get_documenter(app, attr, obj) + except KeyError: + continue if typ is None or documenter.objtype == typ: items.append(name) elif typ == 'attribute' and documenter.objtype == 'property': diff --git a/sphinx_automodapi/tests/test_autodoc_enhancements.py b/sphinx_automodapi/tests/test_autodoc_enhancements.py index 5e3f6de..e60380a 100644 --- a/sphinx_automodapi/tests/test_autodoc_enhancements.py +++ b/sphinx_automodapi/tests/test_autodoc_enhancements.py @@ -41,3 +41,20 @@ def test_type_attrgetter(): assert type_object_attrgetter(MyClass, 'susy', 'default') == 'default' assert type_object_attrgetter(MyClass, '__dict__') == MyClass.__dict__ + + +def test_type_attrgetter_for_dataclass(): + """ + This tests the attribute getter for non-default dataclass fields + """ + import dataclasses + + @dataclasses.dataclass + class MyDataclass: + foo: int + bar: str = "bar value" + + with pytest.raises(AttributeError): + getattr(MyDataclass, 'foo') + assert type_object_attrgetter(MyDataclass, 'foo') == 'foo' + assert getattr(MyDataclass, 'bar') == 'bar value'