diff --git a/CHANGELOG.md b/CHANGELOG.md index d64bb2a6..12ea0ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## Unreleased: pdoc next +- Fix overridden methods being shown as inherited when a class mixes a `Generic[...]` base with a plain base class. + ([#887](https://github.com/mitmproxy/pdoc/pull/887), @SAY-5) - Fix incorrect ordering of `start-after`/`end-before` in RST include directives. ([#876](https://github.com/mitmproxy/pdoc/pull/876), @JohanKarlbergg) - Support Pydantic [`computed_field`](https://docs.pydantic.dev/2.0/usage/computed_fields/) descriptions ([#855](https://github.com/mitmproxy/pdoc/pull/855), @avhz) diff --git a/pdoc/doc.py b/pdoc/doc.py index 41c0d673..c2c6d1ae 100644 --- a/pdoc/doc.py +++ b/pdoc/doc.py @@ -683,9 +683,15 @@ def _bases(self) -> tuple[type, ...]: # __mro__ and __orig_bases__ differ between Python versions and special cases like TypedDict/NamedTuple. # This here is a pragmatic approximation of what we want. + # Only prepend __orig_bases__ entries absent from __mro__, so a plain base cannot shadow the class itself. + mro = self.obj.__mro__ return ( - *(base for base in orig_bases if isinstance(base, type)), - *self.obj.__mro__, + *( + base + for base in orig_bases + if isinstance(base, type) and base not in mro + ), + *mro, ) @cached_property diff --git a/test/test_doc.py b/test/test_doc.py index 3fcf96d4..56dd0fdf 100644 --- a/test/test_doc.py +++ b/test/test_doc.py @@ -186,3 +186,19 @@ def test_source_file_method(): assert m.members["Foo"].members["a_cached_function"].source_file == ( here / "testdata" / "demo_long.py" ) + + +def test_overridden_method_with_generic_base(): + mod = extract.load_module( + extract.parse_spec(here / "testdata" / "generic_mixin.py") + ) + + m = Module(mod) + cls = m.members["Mixed"] + assert isinstance(cls, Class) + + for name in ("generic_method", "plain_method"): + member = cls.members[name] + assert member.taken_from == ("generic_mixin", f"Mixed.{name}") + assert member.docstring == f"{name} implementation docstring" + assert member in cls.own_members diff --git a/test/testdata/generic_mixin.py b/test/testdata/generic_mixin.py new file mode 100644 index 00000000..a1566b2c --- /dev/null +++ b/test/testdata/generic_mixin.py @@ -0,0 +1,23 @@ +from abc import ABC +from typing import Generic +from typing import TypeVar + +T = TypeVar("T") + + +class GenericBase(ABC, Generic[T]): + def generic_method(self): + pass + + +class PlainBase(ABC): + def plain_method(self): + pass + + +class Mixed(GenericBase[T], PlainBase): + def generic_method(self): + """generic_method implementation docstring""" + + def plain_method(self): + """plain_method implementation docstring"""