Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion pdoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,19 @@ def maybe_replace_reprs(a):
return str(inspect.formatannotation(maybe_replace_reprs(annot)))


class _PermissiveFallback:
"""
Returns `Any` for missing attributes (used for annotation-only
modules where the installed version is missingm newer names, e.g.
`typing_extensions.CapsuleType` on Python < 3.13).
"""
def __init__(self, mod):
self._mod = mod

def __getattr__(self, name):
return getattr(self._mod, name, Any)


class Function(Doc):
"""
Representation of documentation for a function or method.
Expand Down Expand Up @@ -1648,9 +1661,30 @@ def _signature_from_string(self):
if strings:
string = filter(strings[0])
_locals, _globals = {}, {}
_globals.update({'capsule': None}) # pybind11 capsule data type
_globals.update({'capsule': Any}) # pybind11 capsule data type

# pybind11 >= 3.0.0 uses typing.SupportsInt, etc.
_globals['typing'] = typing
_globals.update(typing.__dict__)

# types is optional and may lack newer names
# (e.g. types.CapsuleType) depending on installed version
try:
import types
_globals['types'] = _PermissiveFallback(types)
except ImportError:
pass

# typing_extensions is optional and may lack newer names
# (e.g. typing_extensions.CapsuleType) depending on installed version
try:
import typing_extensions
_globals['typing_extensions'] = _PermissiveFallback(typing_extensions)
except ImportError:
pass

_globals.update(self.module.obj.__dict__)

# Trim binding module basename from type annotations
# See: https://github.com/pdoc3/pdoc/pull/148#discussion_r407114141
module_basename = self.module.name.rsplit('.', maxsplit=1)[-1]
Expand Down
96 changes: 96 additions & 0 deletions pdoc/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import doctest
import enum
import importlib.util
import inspect
import os
import shutil
Expand Down Expand Up @@ -104,6 +105,13 @@ def wrapper(*args, **kwargs):
return wrapper


def has_typing_extensions_capsule_type():
if importlib.util.find_spec('typing_extensions') is None:
return False
import typing_extensions
return hasattr(typing_extensions, 'CapsuleType')


class CliTest(unittest.TestCase):
"""
Command-line interface unit tests.
Expand Down Expand Up @@ -1018,6 +1026,94 @@ def f() -> typing.List[typing.Union[str, pdoc.Doc]]: return [] # noqa: E704
func = pdoc.Function('f', DUMMY_PDOC_MODULE, f)
self.assertEqual(func.return_annotation(), 'List[str\N{NBSP}|\N{NBSP}pdoc.Doc]')

def test_Function_signature_from_string(self):
fake_module = ModuleType('fake_pybind11_module')
# sanity check the isolation itself
assert 'typing' not in fake_module.__dict__
mod = pdoc.Module(fake_module)

# Union types with `|` operator where introduces with Python 3.10
if sys.version_info >= (3, 10):
class pybind11_int:
"""
pybind11_int(self: str, pos: typing.SupportsInt | typing.SupportsIndex) -> \
tuple[int, float]
"""
func = pdoc.Function('pybind11_int', mod, pybind11_int)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_int(
self: str, pos: typing.SupportsInt | typing.SupportsIndex) \
-> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_int))

class pybind11_float:
"""
pybind11_float(self: str, pos: typing.SupportsFloat | typing.SupportsIndex) -> \
tuple[int, float]
"""
func = pdoc.Function('pybind11_float', mod, pybind11_float)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_float(
self: str, pos: typing.SupportsFloat | typing.SupportsIndex) \
-> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_float))
else:
class pybind11_int:
"""
pybind11_int(self: str, pos: typing.SupportsInt) -> tuple[int, float]
"""
func = pdoc.Function('pybind11_int', mod, pybind11_int)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_int(self: str, pos: typing.SupportsInt) -> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_int))

class pybind11_float:
"""
pybind11_float(self: str, pos: typing.SupportsFloat) -> tuple[int, float]
"""
func = pdoc.Function('pybind11_float', mod, pybind11_float)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_float(self: str, pos: typing.SupportsFloat) -> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_float))

if sys.version_info >= (3, 13):
import types

class pybind11_capsule:
"""
pybind11_capsule(self: str, pointer: types.CapsuleType) -> tuple[int, float]
"""
func = pdoc.Function('pybind11_capsule', mod, pybind11_capsule)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_capsule(
self: str, pointer: types.CapsuleType) -> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_capsule))
elif has_typing_extensions_capsule_type():
import typing_extensions

class pybind11_capsule:
"""
pybind11_capsule(self: str, pointer: typing_extensions.CapsuleType) -> \
tuple[int, float]
"""
func = pdoc.Function('pybind11_capsule', mod, pybind11_capsule)
sig = pdoc.Function._signature_from_string(func)

def expected_pybind11_capsule(
self: str, pointer: typing_extensions.CapsuleType) -> tuple[int, float]:
raise NotImplementedError
self.assertEqual(sig, inspect.signature(expected_pybind11_capsule))

@ignore_warnings
def test_Variable_type_annotation(self):
class Foobar:
Expand Down
Loading