From ec89d70a79b25a60cc879d0f265c4dc0c0d201b5 Mon Sep 17 00:00:00 2001 From: Philipp Sommer Date: Mon, 21 Oct 2024 14:06:26 +0200 Subject: [PATCH 1/4] implement exception documenter --- autodocsumm/__init__.py | 90 ++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/autodocsumm/__init__.py b/autodocsumm/__init__.py index 6219741..b97a611 100755 --- a/autodocsumm/__init__.py +++ b/autodocsumm/__init__.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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)) @@ -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 @@ -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) @@ -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) @@ -596,13 +653,15 @@ class AutoDocSummDirective(SphinxDirective): Usage:: - .. autoclasssum:: + .. autoclasssumm:: + + .. automodsumm:: - .. automodsum:: + .. autoexceptionsumm:: - 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 @@ -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). @@ -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( @@ -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) From 4e51644041e477e2d4e3322bd62110fb38cf397a Mon Sep 17 00:00:00 2001 From: Philipp Sommer Date: Mon, 21 Oct 2024 14:06:36 +0200 Subject: [PATCH 2/4] implement test for exception documenter --- tests/test-root/dummy.py | 12 +++++++++++ tests/test-root/test_autoexceptionsumm.rst | 4 ++++ tests/test-root/test_exception.rst | 4 ++++ tests/test_autodocsumm.py | 24 ++++++++++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 tests/test-root/test_autoexceptionsumm.rst create mode 100644 tests/test-root/test_exception.rst diff --git a/tests/test-root/dummy.py b/tests/test-root/dummy.py index f980498..d816860 100644 --- a/tests/test-root/dummy.py +++ b/tests/test-root/dummy.py @@ -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""" diff --git a/tests/test-root/test_autoexceptionsumm.rst b/tests/test-root/test_autoexceptionsumm.rst new file mode 100644 index 0000000..9daa169 --- /dev/null +++ b/tests/test-root/test_autoexceptionsumm.rst @@ -0,0 +1,4 @@ +Autoexceptionsumm of Dummy Exception +==================================== + +.. autoexceptionsumm:: dummy.TestException diff --git a/tests/test-root/test_exception.rst b/tests/test-root/test_exception.rst new file mode 100644 index 0000000..2ce6c71 --- /dev/null +++ b/tests/test-root/test_exception.rst @@ -0,0 +1,4 @@ +Dummy Exception Doc +=================== + +.. autoexception:: dummy.TestException \ No newline at end of file diff --git a/tests/test_autodocsumm.py b/tests/test_autodocsumm.py index 2b01981..d34157c 100644 --- a/tests/test_autodocsumm.py +++ b/tests/test_autodocsumm.py @@ -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" ) @@ -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() From 375b1c9f7ac76555b4b08312e4b70f20ba0392d0 Mon Sep 17 00:00:00 2001 From: Philipp Sommer Date: Mon, 21 Oct 2024 14:15:55 +0200 Subject: [PATCH 3/4] add autoexception to docs --- docs/conf_settings.rst | 5 +++++ docs/demo_exception.rst | 8 ++++++++ docs/dummy.py | 13 +++++++++++++ docs/examples.rst | 14 ++++++++++---- 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 docs/demo_exception.rst diff --git a/docs/conf_settings.rst b/docs/conf_settings.rst index 7f3e923..51b1a77 100644 --- a/docs/conf_settings.rst +++ b/docs/conf_settings.rst @@ -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. diff --git a/docs/demo_exception.rst b/docs/demo_exception.rst new file mode 100644 index 0000000..473a1de --- /dev/null +++ b/docs/demo_exception.rst @@ -0,0 +1,8 @@ +.. _demo_exception: + +Demo Exception +============== + +.. autoexception:: dummy.MyException + :members: + :noindex: \ No newline at end of file diff --git a/docs/dummy.py b/docs/dummy.py index 3383ced..d169088 100644 --- a/docs/dummy.py +++ b/docs/dummy.py @@ -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' diff --git a/docs/examples.rst b/docs/examples.rst index 14d274b..5d730d9 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -8,6 +8,7 @@ Examples Demo Module Demo Class + Demo Exception Demo Grouper Including a table of contents @@ -24,11 +25,16 @@ The *autosummary* flag introduces a small table of contents. So:: produces :ref:`this `. And:: - .. autoclass:: dummy.SomeClass + .. autoclass:: dummy.MyClass :members: :autosummary: -produces :ref:`this `. +produces :ref:`this `, and for exceptions:: + + .. autoexception:: dummy.MyException + :members: + :autosummary: +produces :ref:`this `. By default, module members are (mainly) grouped according into *Functions*, *Classes* and *Data*, class members are grouped into *Methods* and @@ -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 From db473cf061efe37565cd34ad7ae0400ad6464831 Mon Sep 17 00:00:00 2001 From: Philipp Sommer Date: Mon, 21 Oct 2024 14:18:09 +0200 Subject: [PATCH 4/4] remove tests for sphinx==3.* --- .github/workflows/python-app.yml | 28 ---------------------------- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 9f40900..304131a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -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"' diff --git a/pyproject.toml b/pyproject.toml index f10aeae..387fea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ requires-python = '>= 3.7' dependencies = [ - 'Sphinx >= 2.2, < 9.0', + 'Sphinx >= 4.0, < 9.0', ] [project.urls]