From 2bf3510d85ad3a2c8eefcc03bb8c5aa46b775107 Mon Sep 17 00:00:00 2001 From: Hassan Kibirige Date: Thu, 14 Mar 2024 12:55:36 +0300 Subject: [PATCH] Inherit docstrings from parent classes This change makes it so that subclass methods that do not have docstrings inherit a docstring from the first parent class whose overriden method has a docstring. It also improves the case for the options ```yaml include_empty: false include_inherited: true ``` where inherited subclass methods are omitted if they have no docstring, yet a parent class method has a docstring. e.g. ``` class Base: def m1(): ... """Calculate m1""" def m2(): ... """Calculate m2""" class Derived(Base): def m2(): ... ``` If the intention is to document `Derived` and include the inherited methods, it would be a surprise if `Derived.m2` is omitted or it is included but it has no docstring. As a result to have an option like `include_inherited_docstring` that can be `false` does not align with the users expected intentions. --- pyproject.toml | 1 + quartodoc/autosummary.py | 3 +++ quartodoc/builder/blueprint.py | 3 +++ quartodoc/tests/__snapshots__/test_renderers.ambr | 14 ++++++++++++++ quartodoc/tests/example_class.py | 7 +++++++ quartodoc/tests/test_builder_blueprint.py | 9 ++++++--- 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4e1cc8f6..b689c810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ requires-python = ">=3.9" dependencies = [ "click", "griffe >= 0.33", + "griffe-inherited-docstrings", "sphobjinv >= 2.3.1", "tabulate >= 0.9.0", "importlib-metadata >= 5.1.0", diff --git a/quartodoc/autosummary.py b/quartodoc/autosummary.py index c02e4623..8f294780 100644 --- a/quartodoc/autosummary.py +++ b/quartodoc/autosummary.py @@ -11,6 +11,8 @@ from griffe.dataclasses import Alias from griffe.docstrings.parsers import Parser, parse from griffe.docstrings import dataclasses as ds # noqa +from griffe.extensions import Extensions +from griffe_inherited_docstrings import InheritDocstringsExtension from griffe import dataclasses as dc from plum import dispatch # noqa from pathlib import Path @@ -123,6 +125,7 @@ def get_object( if loader is None: loader = GriffeLoader( + extensions=Extensions(InheritDocstringsExtension()), docstring_parser=Parser(parser), docstring_options=get_parser_defaults(parser), modules_collection=ModulesCollection(), diff --git a/quartodoc/builder/blueprint.py b/quartodoc/builder/blueprint.py index 24d07f03..9f55204b 100644 --- a/quartodoc/builder/blueprint.py +++ b/quartodoc/builder/blueprint.py @@ -9,6 +9,8 @@ from griffe.collections import ModulesCollection, LinesCollection from griffe.docstrings.parsers import Parser from griffe.exceptions import AliasResolutionError +from griffe.extensions import Extensions +from griffe_inherited_docstrings import InheritDocstringsExtension from functools import partial from textwrap import indent @@ -145,6 +147,7 @@ class BlueprintTransformer(PydanticTransformer): def __init__(self, get_object=None, parser="numpy"): if get_object is None: loader = GriffeLoader( + extensions=Extensions(InheritDocstringsExtension()), docstring_parser=Parser(parser), docstring_options=get_parser_defaults(parser), modules_collection=ModulesCollection(), diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index 60cc8a9b..ad0bced1 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -54,9 +54,16 @@ | Name | Description | | --- | --- | + | [another_method](#quartodoc.tests.example_class.C.another_method) | Another method | | [some_class_method](#quartodoc.tests.example_class.C.some_class_method) | A class method | | [some_method](#quartodoc.tests.example_class.C.some_method) | A method | + ### another_method { #quartodoc.tests.example_class.C.another_method } + + `tests.example_class.C.another_method()` + + Another method + ### some_class_method { #quartodoc.tests.example_class.C.some_class_method } `tests.example_class.C.some_class_method()` @@ -112,9 +119,16 @@ | Name | Description | | --- | --- | + | [another_method](#quartodoc.tests.example_class.C.another_method) | Another method | | [some_class_method](#quartodoc.tests.example_class.C.some_class_method) | A class method | | [some_method](#quartodoc.tests.example_class.C.some_method) | A method | + ## another_method { #quartodoc.tests.example_class.C.another_method } + + `tests.example_class.C.another_method()` + + Another method + ## some_class_method { #quartodoc.tests.example_class.C.some_class_method } `tests.example_class.C.some_class_method()` diff --git a/quartodoc/tests/example_class.py b/quartodoc/tests/example_class.py index 20477a79..25c892e9 100644 --- a/quartodoc/tests/example_class.py +++ b/quartodoc/tests/example_class.py @@ -25,6 +25,9 @@ def __init__(self, x: str, y: int): def some_method(self): """A method""" + def another_method(self): + """Another method""" + @property def some_property(self): """A property""" @@ -41,6 +44,10 @@ class Child(C): def some_new_method(self): """A new method""" + # To test inheriting docstring + def another_method(self): + ... + class AttributesTable: """The short summary. diff --git a/quartodoc/tests/test_builder_blueprint.py b/quartodoc/tests/test_builder_blueprint.py index 02d183c7..7b65792f 100644 --- a/quartodoc/tests/test_builder_blueprint.py +++ b/quartodoc/tests/test_builder_blueprint.py @@ -217,7 +217,7 @@ def _check_member_names(members, expected): [ ("attributes", {"some_property", "z", "SOME_ATTRIBUTE"}), ("classes", {"D"}), - ("functions", {"some_method", "some_class_method"}), + ("functions", {"some_method", "another_method", "some_class_method"}), ], ) def test_blueprint_fetch_members_include_kind_false(kind, removed): @@ -227,6 +227,7 @@ def test_blueprint_fetch_members_include_kind_false(kind, removed): "z", "some_property", "some_method", + "another_method", "D", "some_class_method", } @@ -240,8 +241,10 @@ def test_blueprint_fetch_members_include_inherited(): auto = lo.Auto(name="quartodoc.tests.example_class.Child", include_inherited=True) bp = blueprint(auto) - member_names = set([entry.name for entry in bp.members]) - assert "some_method" in member_names + members = {entry.name: entry for entry in bp.members} + assert "some_method" in members + assert "another_method" in members + assert "Another method" in members["another_method"].obj.docstring.value def test_blueprint_fetch_members_dynamic():