Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autoexception support #104

Merged
merged 4 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,31 +75,3 @@ jobs:
"sphinxcontrib-jsmath<1.0.1"
"sphinxcontrib-qthelp<1.0.7"
"sphinxcontrib-serializinghtml<1.1.10"'


build-legacy-sphinx-30plus:
name: Build

strategy:
fail-fast: false
matrix:
python-version: [ "3.7", "3.8", "3.9" ]
sphinx-version: [
"3.0.*", # possible range: 3.0.0 - 3.5.4
]
include:
- python-version: "3.7"
sphinx-version: "3.5.*" # latest version that supports py3.7
uses: ./.github/workflows/build.yml
with:
python-version: ${{ matrix.python-version }}
extra-requirements: '\
"sphinx==${{ matrix.sphinx-version }}"
"jinja2<3.1"
"alabaster<0.7.14"
"sphinxcontrib-applehelp<1.0.8"
"sphinxcontrib-devhelp<1.0.6"
"sphinxcontrib-htmlhelp<2.0.5"
"sphinxcontrib-jsmath<1.0.1"
"sphinxcontrib-qthelp<1.0.7"
"sphinxcontrib-serializinghtml<1.1.10"'
90 changes: 75 additions & 15 deletions autodocsumm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

from sphinx.ext.autodoc import (
ClassDocumenter, ModuleDocumenter, ALL, PycodeError,
ModuleAnalyzer, AttributeDocumenter, DataDocumenter, Options,
ModuleAnalyzer, AttributeDocumenter, DataDocumenter, Options, ExceptionDocumenter,
Documenter, prepare_docstring)
import sphinx.ext.autodoc as ad

Expand Down Expand Up @@ -349,7 +349,7 @@ class AutoSummModuleDocumenter(ModuleDocumenter, AutosummaryDocumenter):

#: slightly higher priority than
#: :class:`sphinx.ext.autodoc.ModuleDocumenter`
priority = ModuleDocumenter.priority + 0.1
priority = ModuleDocumenter.priority + 0.1 # type: ignore[assignment]

#: original option_spec from :class:`sphinx.ext.autodoc.ModuleDocumenter`
#: but with additional autosummary boolean option
Expand Down Expand Up @@ -399,7 +399,7 @@ class AutoSummClassDocumenter(ClassDocumenter, AutosummaryDocumenter):

#: slightly higher priority than
#: :class:`sphinx.ext.autodoc.ClassDocumenter`
priority = ClassDocumenter.priority + 0.1
priority = ClassDocumenter.priority + 0.1 # type: ignore[assignment]

#: original option_spec from :class:`sphinx.ext.autodoc.ClassDocumenter`
#: but with additional autosummary boolean option
Expand Down Expand Up @@ -437,11 +437,64 @@ def add_content(self, *args, **kwargs):
self.add_autosummary(relative_ref_paths=True)


class AutoSummExceptionDocumenter(ExceptionDocumenter, AutosummaryDocumenter):
"""Exception Documenter with autosummary tables for its members.

This class has the same functionality as the base
:class:`sphinx.ext.autodoc.ExceptionDocumenter` class but with an
additional `autosummary` option to provide the ability to provide a summary
of all methods and attributes.
It's priority is slightly higher than the one of the ExceptionDocumenter
"""

#: slightly higher priority than
#: :class:`sphinx.ext.autodoc.ExceptionDocumenter`
priority = ExceptionDocumenter.priority + 0.1 # type: ignore[assignment]

#: original option_spec from
#: :class:`sphinx.ext.autodoc.ExceptionDocumenter` but with additional
#: autosummary boolean option
option_spec = ExceptionDocumenter.option_spec.copy()
option_spec['autosummary'] = bool_option
option_spec['autosummary-no-nesting'] = bool_option
option_spec['autosummary-sections'] = list_option
option_spec['autosummary-no-titles'] = bool_option
option_spec['autosummary-force-inline'] = bool_option
option_spec['autosummary-nosignatures'] = bool_option

#: Add options for members for the autosummary
for _option in member_options.intersection(option_spec):
option_spec['autosummary-' + _option] = option_spec[_option]
del _option

member_sections = {
ad.ExceptionDocumenter.member_order: 'Classes',
ad.MethodDocumenter.member_order: 'Methods',
ad.AttributeDocumenter.member_order: 'Attributes',
}
""":class:`dict` that includes the autosummary sections

This dictionary defines the sections for the autosummmary option. The
values correspond to the :attr:`sphinx.ext.autodoc.Documenter.member_order`
attribute that shall be used for each section."""

def add_content(self, *args, **kwargs):
super().add_content(*args, **kwargs)

# If the class is already documented under another name, Sphinx
# documents it as data/attribute. In this case, we do not want to
# generate an autosummary of the class for the attribute (see #69).
if not self.doc_as_attr:
self.add_autosummary(relative_ref_paths=True)


class CallableDataDocumenter(DataDocumenter):
""":class:`sphinx.ext.autodoc.DataDocumenter` that uses the __call__ attr
"""

priority = DataDocumenter.priority + 0.1
#: slightly higher priority than
#: :class:`sphinx.ext.autodoc.DataDocumenter`
priority = DataDocumenter.priority + 0.1 # type: ignore[assignment]

def format_args(self):
# for classes, the relevant signature is the __init__ method's
Expand Down Expand Up @@ -474,6 +527,8 @@ def get_doc(self, *args, **kwargs):

doc = []
for docstring in docstrings:
encoding = _get_arg("encoding", 0, None, *args, **kwargs)
ignore = _get_arg("ignore", 1, 1, *args, **kwargs)
if not isinstance(docstring, str):
docstring = force_decode(docstring, encoding)
doc.append(prepare_docstring(docstring, ignore))
Expand All @@ -486,7 +541,9 @@ class CallableAttributeDocumenter(AttributeDocumenter):
attr
"""

priority = AttributeDocumenter.priority + 0.1
#: slightly higher priority than
#: :class:`sphinx.ext.autodoc.AttributeDocumenter`
priority = AttributeDocumenter.priority + 0.1 # type: ignore[assignment]

def format_args(self):
# for classes, the relevant signature is the __init__ method's
Expand Down Expand Up @@ -565,7 +622,7 @@ class NoDataDataDocumenter(CallableDataDocumenter):
"""DataDocumenter that prevents the displaying of large data"""

#: slightly higher priority as the one of the CallableDataDocumenter
priority = CallableDataDocumenter.priority + 0.1
priority = CallableDataDocumenter.priority + 0.1 # type: ignore[assignment]

def __init__(self, *args, **kwargs):
super(NoDataDataDocumenter, self).__init__(*args, **kwargs)
Expand All @@ -580,7 +637,7 @@ class NoDataAttributeDocumenter(CallableAttributeDocumenter):
"""AttributeDocumenter that prevents the displaying of large data"""

#: slightly higher priority as the one of the CallableAttributeDocumenter
priority = CallableAttributeDocumenter.priority + 0.1
priority = CallableAttributeDocumenter.priority + 0.1 # type: ignore[assignment]

def __init__(self, *args, **kwargs):
super(NoDataAttributeDocumenter, self).__init__(*args, **kwargs)
Expand All @@ -596,13 +653,15 @@ class AutoDocSummDirective(SphinxDirective):

Usage::

.. autoclasssum:: <Class>
.. autoclasssumm:: <Class>

.. automodsumm:: <module>

.. automodsum:: <module>
.. autoexceptionsumm:: <ExceptionClass>

The directive additionally supports all options of the ``autoclass`` or
``automod`` directive respectively. Sections can be a list of section titles
to be included. If ommitted, all sections are used.
The directive additionally supports all options of the ``autoclass``,
``automod``, or ``autoexception`` directive respectively. Sections can be a
list of section titles to be included. If ommitted, all sections are used.
"""

has_content = False
Expand All @@ -616,9 +675,9 @@ def run(self):
reporter = self.state.document.reporter

try:
source, lineno = reporter.get_source_and_line(self.lineno)
_, lineno = reporter.get_source_and_line(self.lineno)
except AttributeError:
source, lineno = (None, None)
_, lineno = (None, None)

# look up target Documenter
objtype = self.name[4:-4] # strip prefix (auto-) and suffix (-summ).
Expand Down Expand Up @@ -659,6 +718,7 @@ def setup(app):
app.setup_extension('sphinx.ext.autosummary')
app.setup_extension('sphinx.ext.autodoc')
app.add_directive('autoclasssumm', AutoDocSummDirective)
app.add_directive('autoexceptionsumm', AutoDocSummDirective)
app.add_directive('automodulesumm', AutoDocSummDirective)

AUTODOC_DEFAULT_OPTIONS.extend(
Expand All @@ -673,7 +733,7 @@ def setup(app):
registry = app.registry.documenters
for cls in [AutoSummClassDocumenter, AutoSummModuleDocumenter,
CallableAttributeDocumenter, NoDataDataDocumenter,
NoDataAttributeDocumenter]:
NoDataAttributeDocumenter, AutoSummExceptionDocumenter]:
if not issubclass(registry.get(cls.objtype), cls):
app.add_autodocumenter(cls, override=True)

Expand Down
5 changes: 5 additions & 0 deletions docs/conf_settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ Directives
By default, this directives also sets the `:members:` option unless you
specify `:no-members`.

.. rst:directive:: autoexceptionsumm

The same as the ``autoclasssumm`` directive, just for an ``Exception``
subclass.

.. rst:directive:: automodulesumm

The same as the ``autoclasssumm`` directive, just for a module.
Expand Down
8 changes: 8 additions & 0 deletions docs/demo_exception.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. _demo_exception:

Demo Exception
==============

.. autoexception:: dummy.MyException
:members:
:noindex:
13 changes: 13 additions & 0 deletions docs/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,18 @@ def do_something(self):
some_other_attr = None


class MyException(object):
"""Some Exception

With some description"""

def do_something_exceptional(self):
"""Do something exceptional"""
pass

#: Any instance attribute
some_exception_attr = None


#: Some module data
large_data = 'Whatever'
14 changes: 10 additions & 4 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Examples

Demo Module <demo_module>
Demo Class <demo_class>
Demo Exception <demo_exception>
Demo Grouper <demo_grouper>

Including a table of contents
Expand All @@ -24,11 +25,16 @@ The *autosummary* flag introduces a small table of contents. So::

produces :ref:`this <demo_module>`. And::

.. autoclass:: dummy.SomeClass
.. autoclass:: dummy.MyClass
:members:
:autosummary:

produces :ref:`this <demo_class>`.
produces :ref:`this <demo_class>`, and for exceptions::

.. autoexception:: dummy.MyException
:members:
:autosummary:
produces :ref:`this <demo_exception>`.

By default, module members are (mainly) grouped according into *Functions*,
*Classes* and *Data*, class members are grouped into *Methods* and
Expand Down Expand Up @@ -178,8 +184,8 @@ section of a class, you can specify::
Multiple sections might be separated by `;;`, e.g.
``:autosummary-sections: Methods ;; Attributes``.

This also works for the ``autoclasssumm`` and ``automodulesumm`` directives,
e.g.::
This also works for the ``autoclasssumm``, ``autoexceptionsumm`` and
``automodulesumm`` directives, e.g.::

.. autoclasssumm:: dummy.SomeClass
:autosummary-sections: Methods
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ classifiers = [

requires-python = '>= 3.7'
dependencies = [
'Sphinx >= 2.2, < 9.0',
'Sphinx >= 4.0, < 9.0',
]

[project.urls]
Expand Down
12 changes: 12 additions & 0 deletions tests/test-root/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ class InnerClass(object):
small_data = 'Should be skipped'


class TestException(Exception):
"""Exception test for autosummary"""

def __init__(self):
#: This is an exception attribute
self.exception_instance_attribute = 1

def test_exception_method(self):
"""Test if the method is included"""
pass


class InheritedTestClass(TestClass):
"""Class test for inherited attributes"""

Expand Down
4 changes: 4 additions & 0 deletions tests/test-root/test_autoexceptionsumm.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Autoexceptionsumm of Dummy Exception
====================================

.. autoexceptionsumm:: dummy.TestException
4 changes: 4 additions & 0 deletions tests/test-root/test_exception.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Dummy Exception Doc
===================

.. autoexception:: dummy.TestException
24 changes: 24 additions & 0 deletions tests/test_autodocsumm.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,17 @@ def test_class(self, app):
'DummySection'
)

def test_exception(self, app):
app.build()
html = get_html(app, 'test_exception.html')

if sphinx_version[:2] > [3, 1]:
assert in_autosummary("exception_instance_attribute", html)
elif sphinx_version[:2] < [3, 1]:
assert in_autosummary("TestException.exception_instance_attribute", html)

assert in_autosummary("test_exception_method", html)

@pytest.mark.skipif(
sphinx_version[:2] < [3, 1], reason="Only available for sphinx>=3"
)
Expand Down Expand Up @@ -412,6 +423,19 @@ def test_autoclasssumm(self, app):
assert in_autosummary("test_method", html)
assert in_autosummary("test_attr", html)

def test_autoexceptionsumm(self, app):
"""Test building the autosummary of a class."""
app.build()

html = get_html(app, 'test_autoexceptionsumm.html')

# the class docstring must not be in the html
assert "Class exception for autosummary" not in html

# test if the methods and attributes are there in a table
assert in_autosummary("test_exception_method", html)
assert in_autosummary("exception_instance_attribute", html)

def test_autoclasssumm_no_titles(self, app):
"""Test building the autosummary of a class."""
app.build()
Expand Down
Loading