diff --git a/CHANGELOG.md b/CHANGELOG.md index c52ee93d..da93e31e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Fix a bug where entire modules would be excluded by `--no-include-undocumented`. To exclude modules, see https://pdoc.dev/docs/pdoc.html#exclude-submodules-from-being-documented. ([#728](https://github.com/mitmproxy/pdoc/pull/728), @mhils) +- Fix a bug where pdoc would crash when importing pyi files. + ([#732](https://github.com/mitmproxy/pdoc/pull/732), @mhils) - Fix a bug where subclasses of TypedDict subclasses would not render correctly. ([#729](https://github.com/mitmproxy/pdoc/pull/729), @mhils) diff --git a/pdoc/doc_pyi.py b/pdoc/doc_pyi.py index caf228b5..f1df53b4 100644 --- a/pdoc/doc_pyi.py +++ b/pdoc/doc_pyi.py @@ -6,6 +6,7 @@ from __future__ import annotations +import importlib.util from pathlib import Path import sys import traceback @@ -45,13 +46,28 @@ def find_stub_file(module_name: str) -> Path | None: def _import_stub_file(module_name: str, stub_file: Path) -> types.ModuleType: - """Import the type stub outside of the normal import machinery.""" - code = compile(stub_file.read_text(), str(stub_file), "exec") - m = types.ModuleType(module_name) - m.__file__ = str(stub_file) - eval(code, m.__dict__, m.__dict__) + """ + Import the type stub outside of the normal import machinery. - return m + Note that currently, for objects imported by the stub file, the _original_ module + is used and not the corresponding stub file. + """ + sys.path_hooks.append( + importlib.machinery.FileFinder.path_hook( + (importlib.machinery.SourceFileLoader, [".pyi"]) + ) + ) + try: + loader = importlib.machinery.SourceFileLoader(module_name, str(stub_file)) + spec = importlib.util.spec_from_file_location( + module_name, stub_file, loader=loader + ) + assert spec is not None + m = importlib.util.module_from_spec(spec) + loader.exec_module(m) + return m + finally: + sys.path_hooks.pop() def _prepare_module(ns: doc.Namespace) -> None: diff --git a/test/test_snapshot.py b/test/test_snapshot.py index d2d538b7..04bd8e56 100755 --- a/test/test_snapshot.py +++ b/test/test_snapshot.py @@ -164,7 +164,7 @@ def outfile(self, format: str) -> Path: Snapshot("pyo3_sample_library", specs=["pdoc_pyo3_sample_library"]), Snapshot("top_level_reimports", ["top_level_reimports"]), Snapshot("type_checking_imports", ["type_checking_imports.main"]), - Snapshot("type_stub", min_version=(3, 10)), + Snapshot("type_stubs", ["type_stubs"], min_version=(3, 10)), Snapshot( "visibility", render_options={ diff --git a/test/testdata/type_stub.html b/test/testdata/type_stubs.html similarity index 88% rename from test/testdata/type_stub.html rename to test/testdata/type_stubs.html index eea25d23..e32a54d3 100644 --- a/test/testdata/type_stub.html +++ b/test/testdata/type_stubs.html @@ -4,7 +4,7 @@ - type_stub API documentation + type_stubs API documentation @@ -59,6 +59,12 @@

API Documentation

+
  • + ImportedClass + + +
  • @@ -73,47 +79,57 @@

    API Documentation

    -type_stub

    +type_stubs

    This module has an accompanying .pyi file with type stubs.

    - + - +
     1"""
      2This module has an accompanying .pyi file with type stubs.
      3"""
      4
    - 5
    - 6def func(x, y):
    - 7    """A simple function."""
    - 8
    - 9
    -10var = []
    -11"""A simple variable."""
    -12
    -13
    -14class Class:
    -15    attr = 42
    -16    """An attribute"""
    -17
    -18    def meth(self, y):
    -19        """A simple method."""
    -20
    -21    class Subclass:
    -22        attr = "42"
    -23        """An attribute"""
    -24
    -25        def meth(self, y):
    -26            """A simple method."""
    -27
    -28    def no_type_annotation(self, z):
    -29        """A method not present in the .pyi file."""
    -30
    -31    def overloaded(self, x):
    -32        """An overloaded method."""
    + 5from ._utils import ImportedClass
    + 6
    + 7
    + 8def func(x, y):
    + 9    """A simple function."""
    +10
    +11
    +12var = []
    +13"""A simple variable."""
    +14
    +15
    +16class Class:
    +17    attr = 42
    +18    """An attribute"""
    +19
    +20    def meth(self, y):
    +21        """A simple method."""
    +22
    +23    class Subclass:
    +24        attr = "42"
    +25        """An attribute"""
    +26
    +27        def meth(self, y):
    +28            """A simple method."""
    +29
    +30    def no_type_annotation(self, z):
    +31        """A method not present in the .pyi file."""
    +32
    +33    def overloaded(self, x):
    +34        """An overloaded method."""
    +35
    +36
    +37__all__ = [
    +38    "func",
    +39    "var",
    +40    "Class",
    +41    "ImportedClass",
    +42]
     
    @@ -129,8 +145,8 @@

    -
    7def func(x, y):
    -8    """A simple function."""
    +            
     9def func(x, y):
    +10    """A simple function."""
     
    @@ -164,25 +180,25 @@

    -
    15class Class:
    -16    attr = 42
    -17    """An attribute"""
    -18
    -19    def meth(self, y):
    -20        """A simple method."""
    -21
    -22    class Subclass:
    -23        attr = "42"
    -24        """An attribute"""
    -25
    -26        def meth(self, y):
    -27            """A simple method."""
    -28
    -29    def no_type_annotation(self, z):
    -30        """A method not present in the .pyi file."""
    -31
    -32    def overloaded(self, x):
    -33        """An overloaded method."""
    +            
    17class Class:
    +18    attr = 42
    +19    """An attribute"""
    +20
    +21    def meth(self, y):
    +22        """A simple method."""
    +23
    +24    class Subclass:
    +25        attr = "42"
    +26        """An attribute"""
    +27
    +28        def meth(self, y):
    +29            """A simple method."""
    +30
    +31    def no_type_annotation(self, z):
    +32        """A method not present in the .pyi file."""
    +33
    +34    def overloaded(self, x):
    +35        """An overloaded method."""
     
    @@ -213,8 +229,8 @@

    -
    19    def meth(self, y):
    -20        """A simple method."""
    +            
    21    def meth(self, y):
    +22        """A simple method."""
     
    @@ -234,8 +250,8 @@

    -
    29    def no_type_annotation(self, z):
    -30        """A method not present in the .pyi file."""
    +            
    31    def no_type_annotation(self, z):
    +32        """A method not present in the .pyi file."""
     
    @@ -255,8 +271,8 @@

    -
    32    def overloaded(self, x):
    -33        """An overloaded method."""
    +            
    34    def overloaded(self, x):
    +35        """An overloaded method."""
     
    @@ -277,12 +293,12 @@

    -
    22    class Subclass:
    -23        attr = "42"
    -24        """An attribute"""
    -25
    -26        def meth(self, y):
    -27            """A simple method."""
    +            
    24    class Subclass:
    +25        attr = "42"
    +26        """An attribute"""
    +27
    +28        def meth(self, y):
    +29            """A simple method."""
     
    @@ -313,8 +329,8 @@

    -
    26        def meth(self, y):
    -27            """A simple method."""
    +            
    28        def meth(self, y):
    +29            """A simple method."""
     
    @@ -324,6 +340,27 @@

    +
    + +
    + + class + ImportedClass: + + + +
    + +
    2class ImportedClass:
    +3    """Docstring from imported py file - ideally this should be overridden."""
    +
    + + +

    Docstring from imported py file - ideally this should be overridden.

    +
    + + +
    \ No newline at end of file diff --git a/test/testdata/type_stubs.txt b/test/testdata/type_stubs.txt new file mode 100644 index 00000000..1bdac830 --- /dev/null +++ b/test/testdata/type_stubs.txt @@ -0,0 +1,19 @@ + int: ... # A simple function.> + + + + bool: ... # A simple method.> + + + bool: ... # A simple method.> + > + + + > + + > +> \ No newline at end of file diff --git a/test/testdata/type_stub.py b/test/testdata/type_stubs/__init__.py similarity index 83% rename from test/testdata/type_stub.py rename to test/testdata/type_stubs/__init__.py index e9492dba..82234923 100644 --- a/test/testdata/type_stub.py +++ b/test/testdata/type_stubs/__init__.py @@ -2,6 +2,8 @@ This module has an accompanying .pyi file with type stubs. """ +from ._utils import ImportedClass + def func(x, y): """A simple function.""" @@ -30,3 +32,11 @@ def no_type_annotation(self, z): def overloaded(self, x): """An overloaded method.""" + + +__all__ = [ + "func", + "var", + "Class", + "ImportedClass", +] diff --git a/test/testdata/type_stub.pyi b/test/testdata/type_stubs/__init__.pyi similarity index 81% rename from test/testdata/type_stub.pyi rename to test/testdata/type_stubs/__init__.pyi index fc0f403a..d4ce3e6c 100644 --- a/test/testdata/type_stub.pyi +++ b/test/testdata/type_stubs/__init__.pyi @@ -2,6 +2,8 @@ from typing import Any from typing import Iterable from typing import overload +from ._utils import ImportedClass + def func(x: str, y: Any, z: "Iterable[str]") -> int: ... var: list[str] @@ -21,3 +23,10 @@ class Class: def overloaded(self, x: int) -> int: ... @overload def overloaded(self, x: str) -> str: ... + +__all__ = [ + "func", + "var", + "Class", + "ImportedClass", +] diff --git a/test/testdata/type_stubs/_utils.py b/test/testdata/type_stubs/_utils.py new file mode 100644 index 00000000..0794c820 --- /dev/null +++ b/test/testdata/type_stubs/_utils.py @@ -0,0 +1,2 @@ +class ImportedClass: + """Docstring from imported py file - ideally this should be overridden.""" diff --git a/test/testdata/type_stubs/_utils.pyi b/test/testdata/type_stubs/_utils.pyi new file mode 100644 index 00000000..6eb8bccd --- /dev/null +++ b/test/testdata/type_stubs/_utils.pyi @@ -0,0 +1,2 @@ +class ImportedClass: + """Docstring from imported pyi file"""