Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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