diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 5fd9a73c..1e61bf25 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -19,9 +19,9 @@ import typing from contextlib import contextmanager from copy import copy -from functools import lru_cache, reduce, partial, wraps +from functools import cached_property, lru_cache, reduce, partial, wraps from itertools import tee, groupby -from types import ModuleType +from types import FunctionType, ModuleType from typing import ( # noqa: F401 cast, Any, Callable, Dict, Generator, Iterable, List, Literal, Mapping, NewType, Optional, Set, Tuple, Type, TypeVar, Union, @@ -421,11 +421,21 @@ def _is_descriptor(obj): inspect.ismemberdescriptor(obj)) -def _unwrap_descriptor(obj): +def _unwrap_descriptor(dobj): + obj = dobj.obj if isinstance(obj, property): return (getattr(obj, 'fget', False) or getattr(obj, 'fset', False) or getattr(obj, 'fdel', obj)) + if isinstance(obj, cached_property): + return obj.func + if isinstance(obj, FunctionType): + return obj + if inspect.ismemberdescriptor(obj) or obj.__class__.__name__ == '_tuplegetter': + class_name = dobj.qualname.rsplit('.', 1)[0] + obj = getattr(dobj.module.obj, class_name) + return obj + # XXX: Follow descriptor protocol? Already proved buggy in conditions above return getattr(obj, '__get__', obj) @@ -550,7 +560,7 @@ def source(self) -> str: available, an empty string. """ try: - lines, _ = inspect.getsourcelines(_unwrap_descriptor(self.obj)) + lines, _ = inspect.getsourcelines(_unwrap_descriptor(self)) except (ValueError, TypeError, OSError): return '' return inspect.cleandoc(''.join(['\n'] + lines)) @@ -1402,7 +1412,7 @@ def return_annotation(self, *, link=None) -> str: # global variables lambda: _get_type_hints(not self.cls and self.module.obj)[self.name], # properties - lambda: inspect.signature(_unwrap_descriptor(self.obj)).return_annotation, + lambda: inspect.signature(_unwrap_descriptor(self)).return_annotation, # Use raw annotation strings in unmatched forward declarations lambda: cast(Class, self.cls).obj.__annotations__[self.name], # Extract annotation from the docstring for C builtin function diff --git a/pdoc/html_helpers.py b/pdoc/html_helpers.py index 838ae980..f16f1c16 100644 --- a/pdoc/html_helpers.py +++ b/pdoc/html_helpers.py @@ -8,7 +8,7 @@ import textwrap import traceback from contextlib import contextmanager -from functools import partial, lru_cache, cached_property +from functools import partial, lru_cache from typing import Callable, Match, Optional from warnings import warn import xml.etree.ElementTree as etree @@ -564,20 +564,7 @@ def format_git_link(template: str, dobj: pdoc.Doc): try: if 'commit' in _str_template_fields(template): commit = _git_head_commit() - obj = dobj.obj - - # special handlers for properties, cached_properties, and tuples - if isinstance(obj, property): - obj = obj.fget - elif isinstance(obj, cached_property): - obj = obj.func - elif ( - (hasattr(obj, '__class__') and obj.__class__.__name__ == '_tuplegetter') - or inspect.ismemberdescriptor(obj) - ): - class_name = dobj.qualname.rsplit('.', 1)[0] - obj = getattr(dobj.module.obj, class_name) - + obj = pdoc._unwrap_descriptor(dobj) abs_path = inspect.getfile(inspect.unwrap(obj)) path = _project_relative_path(abs_path)