Skip to content

pdoc treats overridden methods as inherited when mixing Generic[...] base class with non-generic ABC in multiple inheritance #862

@EzekielDaun

Description

@EzekielDaun

Problem Description

When a subclass uses multiple inheritance and one of its base classes is a generic ABC (abc.ABC + typing.Generic[...]), pdoc incorrectly marks an overridden method from a non-generic base class as inherited, and does not show the overriding method’s docstring.

This does not happen for:

  • multiple inheritance from two non-generic ABCs, or
  • multiple inheritance from two generic ABCs.

So the misclassification appears specific to mixing a Generic[...] base with a non-generic ABC base.

Steps to reproduce the behavior:

# foo.py
from abc import ABC
from typing import Generic, TypeVar

T = TypeVar("T")
U = TypeVar("U")


class GenericABC1(ABC, Generic[T]):
    """"""

    def method_generic1(self):
        pass


class GenericABC2(ABC, Generic[U]):
    """"""

    def method_generic2(self):
        pass


class ABC1(ABC):
    """"""

    def method1(self):
        pass


class ABC2(ABC):
    """"""

    def method2(self):
        pass


class NonGenericSubclass(ABC1, ABC2):
    """
    Both methods' docstring are displayed, and are not considered inherited.
    """

    def method1(self):
        """method1 implementation docstring"""
        pass

    def method2(self):
        """method2 implementation docstring"""
        pass


class GenericSubclass1(GenericABC1[T], GenericABC2[U]):
    """
    Both methods' docstring are displayed, and are not considered inherited.
    """

    def method_generic1(self):
        """method_generic1 implementation docstring"""
        pass

    def method_generic2(self):
        """method_generic2 implementation docstring"""
        pass


class GenericSubclass2(GenericABC1[T], ABC1):
    """
    method1's docstring should be displayed, but it is considered inherited. Behavior is different than inheriting either from both non-generic ABC, or from both generic ABC, as above.
    """

    def method_generic1(self):
        """method_generic1 implementation docstring"""
        pass

    def method1(self):
        """method1 implementation docstring"""
        pass


if __name__ == "__main__":
    assert GenericSubclass2.method1 is not ABC1.method1
    assert "method1 implementation" in (GenericSubclass2.method1.__doc__ or "")
pdoc ./foo

Open the generated HTML for module foo and scroll to GenericSubclass2.

Observed Behavior

For GenericSubclass2(GenericABC1[T], ABC1):

  • method_generic1 is shown normally with its docstring.
  • method1 is not shown as a concrete method on GenericSubclass2.
  • Instead, method1 appears under Inherited Members as ABC1.method1().
  • The docstring "method1 implementation docstring" is not displayed as the subclass implementation.
Image
Expected Behavior

Because GenericSubclass2 explicitly defines def method1(self): ..., pdoc should:

  • treat method1 as belonging to GenericSubclass2 (not inherited), and
  • display the overriding method’s docstring.

This is the same behavior pdoc already has for NonGenericSubclass(ABC1, ABC2) and GenericSubclass1(GenericABC1[T], GenericABC2[U]).

System Information

pdoc --version
pdoc: 16.0.0
Python: 3.12.11
Platform: macOS-15.6.1-arm64-arm-64bit

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions