Edit on GitHub

pdoc.doc

This module defines pdoc's documentation objects. A documentation object corresponds to something in your Python code that has a docstring or type annotation. Typically, this only includes modules, classes, functions and methods. However, pdoc adds support for extracting documentation from the abstract syntax tree, which means that variables (module, class or instance) are supported too.

There are four main types of documentation objects:

All documentation types make heavy use of @functools.cached_property decorators. This means they have a large set of attributes that are lazily computed on first access. By convention, all attributes are read-only, although this is not enforced at runtime.

   1"""
   2This module defines pdoc's documentation objects. A documentation object corresponds to *something*
   3in your Python code that has a docstring or type annotation. Typically, this only includes
   4modules, classes, functions and methods. However, `pdoc` adds support for extracting documentation
   5from the abstract syntax tree, which means that variables (module, class or instance) are supported too.
   6
   7There are four main types of documentation objects:
   8
   9- `Module`
  10- `Class`
  11- `Function`
  12- `Variable`
  13
  14All documentation types make heavy use of `@functools.cached_property` decorators.
  15This means they have a large set of attributes that are lazily computed on first access.
  16By convention, all attributes are read-only, although this is not enforced at runtime.
  17"""
  18
  19from __future__ import annotations
  20
  21from abc import ABCMeta
  22from abc import abstractmethod
  23from collections.abc import Callable
  24import dataclasses
  25import enum
  26from functools import cache
  27from functools import cached_property
  28from functools import singledispatchmethod
  29from functools import wraps
  30import inspect
  31import os
  32from pathlib import Path
  33import re
  34import sys
  35import textwrap
  36import traceback
  37import types
  38from typing import Any
  39from typing import ClassVar
  40from typing import Generic
  41from typing import TypeAlias
  42from typing import TypedDict
  43from typing import TypeVar
  44from typing import cast
  45from typing import get_origin
  46from typing import is_typeddict
  47import warnings
  48
  49from pdoc import _pydantic
  50from pdoc import doc_ast
  51from pdoc import doc_pyi
  52from pdoc import extract
  53from pdoc._compat import TypeAliasType
  54from pdoc._compat import formatannotation
  55from pdoc.doc_types import GenericAlias
  56from pdoc.doc_types import NonUserDefinedCallables
  57from pdoc.doc_types import empty
  58from pdoc.doc_types import resolve_annotations
  59from pdoc.doc_types import safe_eval_type
  60
  61
  62def _include_fullname_in_traceback(f):
  63    """
  64    Doc.__repr__ should not raise, but it may raise if we screwed up.
  65    Debugging this is a bit tricky, because, well, we can't repr() in the traceback either then.
  66    This decorator adds location information to the traceback, which helps tracking down bugs.
  67    """
  68
  69    @wraps(f)
  70    def wrapper(self):
  71        try:
  72            return f(self)
  73        except Exception as e:
  74            raise RuntimeError(f"Error in {self.fullname}'s repr!") from e
  75
  76    return wrapper
  77
  78
  79T = TypeVar("T")
  80
  81
  82class Doc(Generic[T]):
  83    """
  84    A base class for all documentation objects.
  85    """
  86
  87    modulename: str
  88    """
  89    The module that this object is in, for example `pdoc.doc`.
  90    """
  91
  92    qualname: str
  93    """
  94    The qualified identifier name for this object. For example, if we have the following code:
  95    
  96    ```python
  97    class Foo:
  98        def bar(self):
  99            pass
 100    ```
 101    
 102    The qualname of `Foo`'s `bar` method is `Foo.bar`. The qualname of the `Foo` class is just `Foo`.
 103    
 104    See <https://www.python.org/dev/peps/pep-3155/> for details.
 105    """
 106
 107    obj: T
 108    """
 109    The underlying Python object.
 110    """
 111
 112    taken_from: tuple[str, str]
 113    """
 114    `(modulename, qualname)` of this doc object's original location.
 115    In the context of a module, this points to the location it was imported from,
 116    in the context of classes, this points to the class an attribute is inherited from.
 117    """
 118
 119    kind: ClassVar[str]
 120    """
 121    The type of the doc object, either `"module"`, `"class"`, `"function"`, or `"variable"`.
 122    """
 123
 124    @property
 125    def type(self) -> str:  # pragma: no cover
 126        warnings.warn(
 127            "pdoc.doc.Doc.type is deprecated. Use pdoc.doc.Doc.kind instead.",
 128            DeprecationWarning,
 129        )
 130        return self.kind
 131
 132    def __init__(
 133        self, modulename: str, qualname: str, obj: T, taken_from: tuple[str, str]
 134    ):
 135        """
 136        Initializes a documentation object, where
 137        `modulename` is the name this module is defined in,
 138        `qualname` contains a dotted path leading to the object from the module top-level, and
 139        `obj` is the object to document.
 140        """
 141        self.modulename = modulename
 142        self.qualname = qualname
 143        self.obj = obj
 144        self.taken_from = taken_from
 145
 146    @cached_property
 147    def fullname(self) -> str:
 148        """The full qualified name of this doc object, for example `pdoc.doc.Doc`."""
 149        # qualname is empty for modules
 150        return f"{self.modulename}.{self.qualname}".rstrip(".")
 151
 152    @cached_property
 153    def name(self) -> str:
 154        """The name of this object. For top-level functions and classes, this is equal to the qualname attribute."""
 155        return self.fullname.split(".")[-1]
 156
 157    @cached_property
 158    def docstring(self) -> str:
 159        """
 160        The docstring for this object. It has already been cleaned by `inspect.cleandoc`.
 161
 162        If no docstring can be found, an empty string is returned.
 163        """
 164        return _safe_getdoc(self.obj)
 165
 166    @cached_property
 167    def source(self) -> str:
 168        """
 169        The source code of the Python object as a `str`.
 170
 171        If the source cannot be obtained (for example, because we are dealing with a native C object),
 172        an empty string is returned.
 173        """
 174        return doc_ast.get_source(self.obj)
 175
 176    @cached_property
 177    def source_file(self) -> Path | None:
 178        """The name of the Python source file in which this object was defined. `None` for built-in objects."""
 179        try:
 180            return Path(inspect.getsourcefile(self.obj) or inspect.getfile(self.obj))  # type: ignore
 181        except TypeError:
 182            return None
 183
 184    @cached_property
 185    def source_lines(self) -> tuple[int, int] | None:
 186        """
 187        Return a `(start, end)` line number tuple for this object.
 188
 189        If no source file can be found, `None` is returned.
 190        """
 191        try:
 192            lines, start = inspect.getsourcelines(self.obj)  # type: ignore
 193            return start, start + len(lines) - 1
 194        except Exception:
 195            return None
 196
 197    @cached_property
 198    def is_inherited(self) -> bool:
 199        """
 200        If True, the doc object is inherited from another location.
 201        This most commonly refers to methods inherited by a subclass,
 202        but can also apply to variables that are assigned a class defined
 203        in a different module.
 204        """
 205        return (self.modulename, self.qualname) != self.taken_from
 206
 207    def __lt__(self, other):
 208        assert isinstance(other, Doc)
 209        return self.fullname.replace("__init__", "").__lt__(
 210            other.fullname.replace("__init__", "")
 211        )
 212
 213
 214U = TypeVar("U", bound=types.ModuleType | type)
 215
 216
 217class Namespace(Doc[U], metaclass=ABCMeta):
 218    """
 219    A documentation object that can have children. In other words, either a module or a class.
 220    """
 221
 222    @cached_property
 223    @abstractmethod
 224    def _member_objects(self) -> dict[str, Any]:
 225        """
 226        A mapping from *all* public and private member names to their Python objects.
 227        """
 228
 229    @cached_property
 230    @abstractmethod
 231    def _var_docstrings(self) -> dict[str, str]:
 232        """A mapping from some member variable names to their docstrings."""
 233
 234    @cached_property
 235    @abstractmethod
 236    def _func_docstrings(self) -> dict[str, str]:
 237        """A mapping from some member function names to their raw (not processed by any @decorators) docstrings."""
 238
 239    @cached_property
 240    @abstractmethod
 241    def _var_annotations(self) -> dict[str, Any]:
 242        """A mapping from some member variable names to their type annotations."""
 243
 244    @abstractmethod
 245    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
 246        """The location this member was taken from. If unknown, (modulename, qualname) is returned."""
 247
 248    @cached_property
 249    @abstractmethod
 250    def own_members(self) -> list[Doc]:
 251        """A list of all own (i.e. non-inherited) `members`."""
 252
 253    @cached_property
 254    def members(self) -> dict[str, Doc]:
 255        """A mapping from all members to their documentation objects.
 256
 257        This mapping includes private members; they are only filtered out as part of the template logic.
 258        Constructors for enums, dicts, and abstract base classes are not picked up unless they have a custom docstring.
 259        """
 260        members: dict[str, Doc] = {}
 261        for name, obj in self._member_objects.items():
 262            qualname = f"{self.qualname}.{name}".lstrip(".")
 263            taken_from = self._taken_from(name, obj)
 264            doc: Doc[Any]
 265
 266            is_classmethod = isinstance(obj, classmethod)
 267            is_property = (
 268                isinstance(obj, (property, cached_property))
 269                or
 270                # Python 3.9 - 3.10: @classmethod @property is allowed.
 271                is_classmethod
 272                and isinstance(obj.__func__, (property, cached_property))
 273            )
 274            if is_property:
 275                func = obj
 276                if is_classmethod:
 277                    func = obj.__func__
 278                if isinstance(func, property):
 279                    func = func.fget
 280                else:
 281                    assert isinstance(func, cached_property)
 282                    func = func.func
 283
 284                doc_f = Function(self.modulename, qualname, func, taken_from)
 285                doc = Variable(
 286                    self.modulename,
 287                    qualname,
 288                    docstring=doc_f.docstring,
 289                    annotation=doc_f.signature.return_annotation,
 290                    default_value=empty,
 291                    taken_from=taken_from,
 292                )
 293                doc.source = doc_f.source
 294                doc.source_file = doc_f.source_file
 295                doc.source_lines = doc_f.source_lines
 296            elif inspect.isroutine(obj):
 297                doc = Function(self.modulename, qualname, obj, taken_from)  # type: ignore
 298            elif (
 299                inspect.isclass(obj)
 300                and obj is not empty
 301                and not isinstance(obj, GenericAlias)
 302                and obj.__qualname__.rpartition(".")[2] == qualname.rpartition(".")[2]
 303            ):
 304                # `dict[str,str]` is a GenericAlias instance. We want to render type aliases as variables though.
 305                doc = Class(self.modulename, qualname, obj, taken_from)
 306            elif inspect.ismodule(obj):
 307                if os.environ.get("PDOC_SUBMODULES"):  # pragma: no cover
 308                    doc = Module.from_name(obj.__name__)
 309                else:
 310                    continue
 311            elif inspect.isdatadescriptor(obj):
 312                doc = Variable(
 313                    self.modulename,
 314                    qualname,
 315                    docstring=getattr(obj, "__doc__", None) or "",
 316                    annotation=self._var_annotations.get(name, empty),
 317                    default_value=empty,
 318                    taken_from=taken_from,
 319                )
 320            else:
 321                doc = Variable(
 322                    self.modulename,
 323                    qualname,
 324                    docstring="",
 325                    annotation=self._var_annotations.get(name, empty),
 326                    default_value=_pydantic.default_value(self.obj, name, obj),
 327                    taken_from=taken_from,
 328                )
 329
 330            if _doc := _pydantic.get_field_docstring(cast(type, self.obj), name):
 331                doc.docstring = _doc
 332            elif self._var_docstrings.get(name):
 333                doc.docstring = self._var_docstrings[name]
 334            elif self._func_docstrings.get(name) and not doc.docstring:
 335                doc.docstring = self._func_docstrings[name]
 336
 337            members[doc.name] = doc
 338
 339        if isinstance(self, Module):
 340            # quirk: doc_pyi expects .members to be set already
 341            self.members = members  # type: ignore
 342            doc_pyi.include_typeinfo_from_stub_files(self)
 343
 344        return members
 345
 346    @cached_property
 347    def _members_by_origin(self) -> dict[tuple[str, str], list[Doc]]:
 348        """A mapping from (modulename, qualname) locations to the attributes taken from that path"""
 349        locations: dict[tuple[str, str], list[Doc]] = {}
 350        for member in self.members.values():
 351            mod, qualname = member.taken_from
 352            parent_qualname = ".".join(qualname.rsplit(".", maxsplit=1)[:-1])
 353            locations.setdefault((mod, parent_qualname), [])
 354            locations[(mod, parent_qualname)].append(member)
 355        return locations
 356
 357    @cached_property
 358    def inherited_members(self) -> dict[tuple[str, str], list[Doc]]:
 359        """A mapping from (modulename, qualname) locations to the attributes inherited from that path"""
 360        return {
 361            k: v
 362            for k, v in self._members_by_origin.items()
 363            if k not in (self.taken_from, (self.modulename, self.qualname))
 364        }
 365
 366    @cached_property
 367    def flattened_own_members(self) -> list[Doc]:
 368        """
 369        A list of all documented members and their child classes, recursively.
 370        """
 371        flattened = []
 372        for x in self.own_members:
 373            flattened.append(x)
 374            if isinstance(x, Class):
 375                flattened.extend(
 376                    [cls for cls in x.flattened_own_members if isinstance(cls, Class)]
 377                )
 378        return flattened
 379
 380    @cache
 381    def get(self, identifier: str) -> Doc | None:
 382        """Returns the documentation object for a particular identifier, or `None` if the identifier cannot be found."""
 383        head, _, tail = identifier.partition(".")
 384        if tail:
 385            h = self.members.get(head, None)
 386            if isinstance(h, Class):
 387                return h.get(tail)
 388            return None
 389        else:
 390            return self.members.get(identifier, None)
 391
 392
 393class Module(Namespace[types.ModuleType]):
 394    """
 395    Representation of a module's documentation.
 396    """
 397
 398    def __init__(
 399        self,
 400        module: types.ModuleType,
 401    ):
 402        """
 403        Creates a documentation object given the actual
 404        Python module object.
 405        """
 406        super().__init__(module.__name__, "", module, (module.__name__, ""))
 407
 408    kind = "module"
 409
 410    @classmethod
 411    @cache
 412    def from_name(cls, name: str) -> Module:
 413        """Create a `Module` object by supplying the module's (full) name."""
 414        return cls(extract.load_module(name))
 415
 416    @cache
 417    @_include_fullname_in_traceback
 418    def __repr__(self):
 419        return f"<module {self.fullname}{_docstr(self)}{_children(self)}>"
 420
 421    @cached_property
 422    def is_package(self) -> bool:
 423        """
 424        `True` if the module is a package, `False` otherwise.
 425
 426        Packages are a special kind of module that may have submodules.
 427        Typically, this means that this file is in a directory named like the
 428        module with the name `__init__.py`.
 429        """
 430        return _safe_getattr(self.obj, "__path__", None) is not None
 431
 432    @cached_property
 433    def _var_docstrings(self) -> dict[str, str]:
 434        return doc_ast.walk_tree(self.obj).var_docstrings
 435
 436    @cached_property
 437    def _func_docstrings(self) -> dict[str, str]:
 438        return doc_ast.walk_tree(self.obj).func_docstrings
 439
 440    @cached_property
 441    def _var_annotations(self) -> dict[str, Any]:
 442        annotations = doc_ast.walk_tree(self.obj).annotations.copy()
 443        for k, v in _safe_getattr(self.obj, "__annotations__", {}).items():
 444            annotations[k] = v
 445
 446        return resolve_annotations(annotations, self.obj, None, self.fullname)
 447
 448    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
 449        if obj is empty:
 450            return self.modulename, f"{self.qualname}.{member_name}".lstrip(".")
 451        if isinstance(obj, types.ModuleType):
 452            return obj.__name__, ""
 453
 454        mod = _safe_getattr(obj, "__module__", None)
 455        qual = _safe_getattr(obj, "__qualname__", None)
 456        if mod and isinstance(qual, str) and "<locals>" not in qual:
 457            return mod, qual
 458        else:
 459            # This might be wrong, but it's the best guess we have.
 460            return (mod or self.modulename), f"{self.qualname}.{member_name}".lstrip(
 461                "."
 462            )
 463
 464    @cached_property
 465    def own_members(self) -> list[Doc]:
 466        return list(self.members.values())
 467
 468    @cached_property
 469    def submodules(self) -> list[Module]:
 470        """A list of all (direct) submodules."""
 471        include: Callable[[str], bool]
 472        mod_all = _safe_getattr(self.obj, "__all__", False)
 473        if mod_all is not False:
 474            mod_all_pos = {name: i for i, name in enumerate(mod_all)}
 475            include = mod_all_pos.__contains__
 476        else:
 477
 478            def include(name: str) -> bool:
 479                # optimization: we don't even try to load modules starting with an underscore as they would not be
 480                # visible by default. The downside of this is that someone who overrides `is_public` will miss those
 481                # entries, the upsides are 1) better performance and 2) less warnings because of import failures
 482                # (think of OS-specific modules, e.g. _linux.py failing to import on Windows).
 483                return not name.startswith("_")
 484
 485        submodules: list[Module] = []
 486        for mod_name, mod in extract.iter_modules2(self.obj).items():
 487            if not include(mod_name):
 488                continue
 489            try:
 490                module = Module.from_name(mod.name)
 491            except RuntimeError:
 492                warnings.warn(f"Couldn't import {mod.name}:\n{traceback.format_exc()}")
 493                continue
 494            submodules.append(module)
 495
 496        if mod_all:
 497            submodules = sorted(submodules, key=lambda m: mod_all_pos[m.name])
 498
 499        return submodules
 500
 501    @cached_property
 502    def _ast_keys(self) -> set[str]:
 503        return (
 504            self._var_docstrings.keys()
 505            | self._func_docstrings.keys()
 506            | self._var_annotations.keys()
 507        )
 508
 509    @cached_property
 510    def _member_objects(self) -> dict[str, Any]:
 511        members = {}
 512
 513        all_: list[str] | None = _safe_getattr(self.obj, "__all__", None)
 514        if all_ is not None:
 515            for name in all_:
 516                if not isinstance(name, str):
 517                    # Gracefully handle the case where objects are directly specified in __all__.
 518                    name = _safe_getattr(name, "__name__", str(name))
 519                if name in self.obj.__dict__:
 520                    val = self.obj.__dict__[name]
 521                elif name in self._var_annotations:
 522                    val = empty
 523                else:
 524                    # this may be an unimported submodule, try importing.
 525                    # (https://docs.python.org/3/tutorial/modules.html#importing-from-a-package)
 526                    try:
 527                        val = extract.load_module(f"{self.modulename}.{name}")
 528                    except RuntimeError as e:
 529                        warnings.warn(
 530                            f"Found {name!r} in {self.modulename}.__all__, but it does not resolve: {e}"
 531                        )
 532                        val = empty
 533                members[name] = val
 534
 535        else:
 536            # Starting with Python 3.10, __annotations__ is created on demand,
 537            # so we make a copy here as obj.__dict__ is changed while we iterate over it.
 538            # Additionally, accessing self._ast_keys may lead to the execution of TYPE_CHECKING blocks,
 539            # which may also modify obj.__dict__. (https://github.com/mitmproxy/pdoc/issues/351)
 540            for name, obj in list(self.obj.__dict__.items()):
 541                # We already exclude everything here that is imported.
 542                obj_module = inspect.getmodule(obj)
 543                declared_in_this_module = self.obj.__name__ == _safe_getattr(
 544                    obj_module, "__name__", None
 545                )
 546                include_in_docs = declared_in_this_module or name in self._ast_keys
 547                if include_in_docs:
 548                    members[name] = obj
 549
 550            for name in self._var_docstrings:
 551                members.setdefault(name, empty)
 552            for name in self._var_annotations:
 553                members.setdefault(name, empty)
 554
 555            members, notfound = doc_ast.sort_by_source(self.obj, {}, members)
 556            members.update(notfound)
 557
 558        return members
 559
 560    @cached_property
 561    def variables(self) -> list[Variable]:
 562        """
 563        A list of all documented module level variables.
 564        """
 565        return [x for x in self.members.values() if isinstance(x, Variable)]
 566
 567    @cached_property
 568    def classes(self) -> list[Class]:
 569        """
 570        A list of all documented module level classes.
 571        """
 572        return [x for x in self.members.values() if isinstance(x, Class)]
 573
 574    @cached_property
 575    def functions(self) -> list[Function]:
 576        """
 577        A list of all documented module level functions.
 578        """
 579        return [x for x in self.members.values() if isinstance(x, Function)]
 580
 581
 582class Class(Namespace[type]):
 583    """
 584    Representation of a class's documentation.
 585    """
 586
 587    kind = "class"
 588
 589    @cache
 590    @_include_fullname_in_traceback
 591    def __repr__(self):
 592        return f"<{_decorators(self)}class {self.modulename}.{self.qualname}{_docstr(self)}{_children(self)}>"
 593
 594    @cached_property
 595    def docstring(self) -> str:
 596        doc = Doc.docstring.__get__(self)  # type: ignore
 597        if doc == dict.__doc__:
 598            # Don't display default docstring for dict subclasses (primarily TypedDict).
 599            return ""
 600        if doc in _Enum_default_docstrings:
 601            # Don't display default docstring for enum subclasses.
 602            return ""
 603        if dataclasses.is_dataclass(self.obj) and doc.startswith(self.obj.__name__):
 604            try:
 605                sig = inspect.signature(self.obj)
 606            except Exception:
 607                pass
 608            else:
 609                # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py
 610                is_dataclass_with_default_docstring = doc == self.obj.__name__ + str(
 611                    sig
 612                ).replace(" -> None", "")
 613                if is_dataclass_with_default_docstring:
 614                    return ""
 615        return doc
 616
 617    @cached_property
 618    def _var_docstrings(self) -> dict[str, str]:
 619        docstrings: dict[str, str] = {}
 620        for cls in self._bases:
 621            for name, docstr in doc_ast.walk_tree(cls).var_docstrings.items():
 622                docstrings.setdefault(name, docstr)
 623        return docstrings
 624
 625    @cached_property
 626    def _func_docstrings(self) -> dict[str, str]:
 627        docstrings: dict[str, str] = {}
 628        for cls in self._bases:
 629            for name, docstr in doc_ast.walk_tree(cls).func_docstrings.items():
 630                docstrings.setdefault(name, docstr)
 631        return docstrings
 632
 633    @cached_property
 634    def _var_annotations(self) -> dict[str, type]:
 635        # this is a bit tricky: __annotations__ also includes annotations from parent classes,
 636        # but we need to execute them in the namespace of the parent class.
 637        # Our workaround for this is to walk the MRO backwards, and only update/evaluate only if the annotation changes.
 638        annotations: dict[
 639            str, tuple[Any, type]
 640        ] = {}  # attribute -> (annotation_unresolved, annotation_resolved)
 641        for cls in reversed(self._bases):
 642            cls_annotations = doc_ast.walk_tree(cls).annotations.copy()
 643            dynamic_annotations = _safe_getattr(cls, "__annotations__", None)
 644            if isinstance(dynamic_annotations, dict):
 645                for attr, unresolved_annotation in dynamic_annotations.items():
 646                    cls_annotations[attr] = unresolved_annotation
 647            cls_fullname = (
 648                (_safe_getattr(cls, "__module__", "") or "") + "." + cls.__qualname__
 649            ).lstrip(".")
 650
 651            new_annotations = {
 652                attr: unresolved_annotation
 653                for attr, unresolved_annotation in cls_annotations.items()
 654                if attr not in annotations
 655                or annotations[attr][0] is not unresolved_annotation
 656            }
 657            localns = _safe_getattr(cls, "__dict__", None)
 658            for attr, t in resolve_annotations(
 659                new_annotations, inspect.getmodule(cls), localns, cls_fullname
 660            ).items():
 661                annotations[attr] = (new_annotations[attr], t)
 662
 663        return {k: v[1] for k, v in annotations.items()}
 664
 665    @cached_property
 666    def _bases(self) -> tuple[type, ...]:
 667        orig_bases = _safe_getattr(self.obj, "__orig_bases__", ())
 668
 669        if is_typeddict(self.obj):
 670            if sys.version_info < (3, 12):  # pragma: no cover
 671                # TypedDicts on Python <3.12 have a botched __mro__. We need to fix it.
 672                return (self.obj, *orig_bases[:-1])
 673            else:
 674                # TypedDict on Python >=3.12 removes intermediate classes from __mro__,
 675                # so we use orig_bases to recover the full mro.
 676                while orig_bases and orig_bases[-1] is not TypedDict:
 677                    parent_bases = _safe_getattr(orig_bases[-1], "__orig_bases__", ())
 678                    if (
 679                        len(parent_bases) != 1 or parent_bases in orig_bases
 680                    ):  # sanity check that things look right
 681                        break  # pragma: no cover
 682                    orig_bases = (*orig_bases, parent_bases[0])
 683
 684        # __mro__ and __orig_bases__ differ between Python versions and special cases like TypedDict/NamedTuple.
 685        # This here is a pragmatic approximation of what we want.
 686        return (
 687            *(base for base in orig_bases if isinstance(base, type)),
 688            *self.obj.__mro__,
 689        )
 690
 691    @cached_property
 692    def _declarations(self) -> dict[str, tuple[str, str]]:
 693        decls: dict[str, tuple[str, str]] = {}
 694        for cls in self._bases:
 695            treeinfo = doc_ast.walk_tree(cls)
 696            for name in (
 697                treeinfo.var_docstrings.keys()
 698                | treeinfo.func_docstrings.keys()
 699                | treeinfo.annotations.keys()
 700            ):
 701                decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}"))
 702            for name in cls.__dict__:
 703                decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}"))
 704        if decls.get("__init__", None) == ("builtins", "object.__init__"):
 705            decls["__init__"] = (
 706                self.obj.__module__,
 707                f"{self.obj.__qualname__}.__init__",
 708            )
 709        return decls
 710
 711    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
 712        try:
 713            return self._declarations[member_name]
 714        except KeyError:  # pragma: no cover
 715            # TypedDict botches __mro__ on Python <3.12 and may need special casing here.
 716            # One workaround is to also specify TypedDict as a base class, see pdoc.doc.Class._bases.
 717            warnings.warn(
 718                f"Cannot determine where {self.fullname}.{member_name} is taken from, assuming current file."
 719            )
 720            return self.modulename, f"{self.qualname}.{member_name}"
 721
 722    @cached_property
 723    def own_members(self) -> list[Doc]:
 724        members = self._members_by_origin.get((self.modulename, self.qualname), [])
 725        if self.taken_from != (self.modulename, self.qualname):
 726            # .taken_from may be != (self.modulename, self.qualname), for example when
 727            # a module re-exports a class from a private submodule.
 728            members += self._members_by_origin.get(self.taken_from, [])
 729        return members
 730
 731    @cached_property
 732    def _member_objects(self) -> dict[str, Any]:
 733        unsorted: dict[str, Any] = {}
 734        for cls in self._bases:
 735            for name, obj in cls.__dict__.items():
 736                unsorted.setdefault(name, obj)
 737        for name in self._var_docstrings:
 738            unsorted.setdefault(name, empty)
 739        for name in self._var_annotations:
 740            unsorted.setdefault(name, empty)
 741
 742        init_has_no_doc = unsorted.get("__init__", object.__init__).__doc__ in (
 743            None,
 744            object.__init__.__doc__,
 745        )
 746        if init_has_no_doc:
 747            if inspect.isabstract(self.obj):
 748                # Special case: We don't want to show constructors for abstract base classes unless
 749                # they have a custom docstring.
 750                del unsorted["__init__"]
 751            elif issubclass(self.obj, enum.Enum):
 752                # Special case: Do not show a constructor for enums. They are typically not constructed by users.
 753                # The alternative would be showing __new__, as __call__ is too verbose.
 754                del unsorted["__init__"]
 755            elif issubclass(self.obj, dict):
 756                # Special case: Do not show a constructor for dict subclasses.
 757                unsorted.pop(
 758                    "__init__", None
 759                )  # TypedDict subclasses may not have __init__.
 760            else:
 761                # Check if there's a helpful Metaclass.__call__ or Class.__new__. This dance is very similar to
 762                # https://github.com/python/cpython/blob/9feae41c4f04ca27fd2c865807a5caeb50bf4fc4/Lib/inspect.py#L2359-L2376
 763                call = _safe_getattr(type(self.obj), "__call__", None)
 764                custom_call_with_custom_docstring = (
 765                    call is not None
 766                    and not isinstance(call, NonUserDefinedCallables)
 767                    and call.__doc__ not in (None, object.__call__.__doc__)
 768                )
 769                if custom_call_with_custom_docstring:
 770                    unsorted["__init__"] = call
 771                else:
 772                    # Does our class define a custom __new__ method?
 773                    new = _safe_getattr(self.obj, "__new__", None)
 774                    custom_new_with_custom_docstring = (
 775                        new is not None
 776                        and not isinstance(new, NonUserDefinedCallables)
 777                        and new.__doc__ not in (None, object.__new__.__doc__)
 778                    )
 779                    if custom_new_with_custom_docstring:
 780                        unsorted["__init__"] = new
 781
 782        sorted: dict[str, Any] = {}
 783        for cls in self._bases:
 784            sorted, unsorted = doc_ast.sort_by_source(cls, sorted, unsorted)
 785        sorted.update(unsorted)
 786
 787        if _pydantic.is_pydantic_model(self.obj):
 788            sorted = {
 789                k: v for k, v in sorted.items() if k not in _pydantic.IGNORED_FIELDS
 790            }
 791
 792        return sorted
 793
 794    @cached_property
 795    def bases(self) -> list[tuple[str, str, str]]:
 796        """
 797        A list of all base classes, i.e. all immediate parent classes.
 798
 799        Each parent class is represented as a `(modulename, qualname, display_text)` tuple.
 800        """
 801        bases = []
 802        for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__):
 803            if x is object:
 804                continue
 805            o = get_origin(x)
 806            if o:
 807                bases.append((o.__module__, o.__qualname__, str(x)))
 808            elif x.__module__ == self.modulename:
 809                bases.append((x.__module__, x.__qualname__, x.__qualname__))
 810            else:
 811                bases.append(
 812                    (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}")
 813                )
 814        return bases
 815
 816    @cached_property
 817    def decorators(self) -> list[str]:
 818        """A list of all decorators the class is decorated with."""
 819        decorators = []
 820        for t in doc_ast.parse(self.obj).decorator_list:
 821            decorators.append(f"@{doc_ast.unparse(t)}")
 822        return decorators
 823
 824    @cached_property
 825    def class_variables(self) -> list[Variable]:
 826        """
 827        A list of all documented class variables in the class.
 828
 829        Class variables are variables that are explicitly annotated with `typing.ClassVar`.
 830        All other variables are treated as instance variables.
 831        """
 832        return [
 833            x
 834            for x in self.members.values()
 835            if isinstance(x, Variable) and x.is_classvar
 836        ]
 837
 838    @cached_property
 839    def instance_variables(self) -> list[Variable]:
 840        """
 841        A list of all instance variables in the class.
 842        """
 843        return [
 844            x
 845            for x in self.members.values()
 846            if isinstance(x, Variable) and not x.is_classvar
 847        ]
 848
 849    @cached_property
 850    def classmethods(self) -> list[Function]:
 851        """
 852        A list of all documented `@classmethod`s.
 853        """
 854        return [
 855            x
 856            for x in self.members.values()
 857            if isinstance(x, Function) and x.is_classmethod
 858        ]
 859
 860    @cached_property
 861    def staticmethods(self) -> list[Function]:
 862        """
 863        A list of all documented `@staticmethod`s.
 864        """
 865        return [
 866            x
 867            for x in self.members.values()
 868            if isinstance(x, Function) and x.is_staticmethod
 869        ]
 870
 871    @cached_property
 872    def methods(self) -> list[Function]:
 873        """
 874        A list of all documented methods in the class that are neither static- nor classmethods.
 875        """
 876        return [
 877            x
 878            for x in self.members.values()
 879            if isinstance(x, Function)
 880            and not x.is_staticmethod
 881            and not x.is_classmethod
 882        ]
 883
 884
 885WrappedFunction = types.FunctionType | staticmethod | classmethod
 886
 887
 888class Function(Doc[types.FunctionType]):
 889    """
 890    Representation of a function's documentation.
 891
 892    This class covers all "flavors" of functions, for example it also
 893    supports `@classmethod`s or `@staticmethod`s.
 894    """
 895
 896    kind = "function"
 897
 898    wrapped: WrappedFunction
 899    """The original wrapped function (e.g., `staticmethod(func)`)"""
 900
 901    obj: types.FunctionType
 902    """The unwrapped "real" function."""
 903
 904    def __init__(
 905        self,
 906        modulename: str,
 907        qualname: str,
 908        func: WrappedFunction,
 909        taken_from: tuple[str, str],
 910    ):
 911        """Initialize a function's documentation object."""
 912        unwrapped: types.FunctionType
 913        if isinstance(func, (classmethod, staticmethod)):
 914            unwrapped = func.__func__  # type: ignore
 915        elif isinstance(func, singledispatchmethod):
 916            unwrapped = func.func  # type: ignore
 917        elif hasattr(func, "__wrapped__"):
 918            unwrapped = func.__wrapped__
 919        else:
 920            unwrapped = func
 921        super().__init__(modulename, qualname, unwrapped, taken_from)
 922        self.wrapped = func  # type: ignore
 923
 924    @cache
 925    @_include_fullname_in_traceback
 926    def __repr__(self):
 927        if self.is_classmethod:
 928            t = "class"
 929        elif self.is_staticmethod:
 930            t = "static"
 931        elif self.qualname != _safe_getattr(self.obj, "__name__", None):
 932            t = "method"
 933        else:
 934            t = "function"
 935        return f"<{_decorators(self)}{t} {self.funcdef} {self.name}{self.signature}: ...{_docstr(self)}>"
 936
 937    @cached_property
 938    def docstring(self) -> str:
 939        doc = Doc.docstring.__get__(self)  # type: ignore
 940        if not doc:
 941            # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors.
 942            # We now do an ugly dance to obtain the bound object instead,
 943            # that somewhat resembles what inspect._findclass is doing.
 944            cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None)
 945            for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]:
 946                cls = _safe_getattr(cls, name, None)
 947
 948            unbound = _safe_getattr(cls, "__dict__", {}).get(self.name)
 949            is_classmethod_property = isinstance(unbound, classmethod) and isinstance(
 950                unbound.__func__, (property, cached_property)
 951            )
 952            if not is_classmethod_property:
 953                # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11.
 954                # Directly accessing them would give us the return value, which has the wrong docstring.
 955                doc = _safe_getdoc(_safe_getattr(cls, self.name, None))
 956
 957        if doc == object.__init__.__doc__:
 958            # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined...
 959            return ""
 960        else:
 961            return doc
 962
 963    @cached_property
 964    def is_classmethod(self) -> bool:
 965        """
 966        `True` if this function is a `@classmethod`, `False` otherwise.
 967        """
 968        return isinstance(self.wrapped, classmethod)
 969
 970    @cached_property
 971    def is_staticmethod(self) -> bool:
 972        """
 973        `True` if this function is a `@staticmethod`, `False` otherwise.
 974        """
 975        return isinstance(self.wrapped, staticmethod)
 976
 977    @cached_property
 978    def decorators(self) -> list[str]:
 979        """A list of all decorators the function is decorated with."""
 980        decorators = []
 981        obj: types.FunctionType = self.obj  # type: ignore
 982        for t in doc_ast.parse(obj).decorator_list:
 983            decorators.append(f"@{doc_ast.unparse(t)}")
 984        return decorators
 985
 986    @cached_property
 987    def funcdef(self) -> str:
 988        """
 989        The string of keywords used to define the function, i.e. `"def"` or `"async def"`.
 990        """
 991        if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction(
 992            self.obj
 993        ):
 994            return "async def"
 995        else:
 996            return "def"
 997
 998    @cached_property
 999    def signature(self) -> inspect.Signature:
1000        """
1001        The function's signature.
1002
1003        This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature`
1004        that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines
1005        in repr(). Additionally, all types are already resolved.
1006
1007        If the signature cannot be determined, a placeholder Signature object is returned.
1008        """
1009        if self.obj is object.__init__:
1010            # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs)
1011            # signature for the default __init__ method.
1012            return inspect.Signature()
1013        try:
1014            sig = _PrettySignature.from_callable(self.obj)
1015        except Exception:
1016            return inspect.Signature(
1017                [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)]
1018            )
1019        mod = inspect.getmodule(self.obj)
1020        globalns = _safe_getattr(mod, "__dict__", {})
1021        localns = globalns
1022        for parent_cls_name in self.qualname.split(".")[:-1]:
1023            parent_cls = localns.get(parent_cls_name, object)
1024            localns = _safe_getattr(parent_cls, "__dict__", None)
1025            if localns is None:
1026                break  # pragma: no cover
1027
1028        if self.name == "__init__":
1029            sig = sig.replace(return_annotation=empty)
1030        else:
1031            sig = sig.replace(
1032                return_annotation=safe_eval_type(
1033                    sig.return_annotation, globalns, localns, mod, self.fullname
1034                )
1035            )
1036        for p in sig.parameters.values():
1037            p._annotation = safe_eval_type(  # type: ignore
1038                p.annotation, globalns, localns, mod, self.fullname
1039            )
1040        return sig
1041
1042    @cached_property
1043    def signature_without_self(self) -> inspect.Signature:
1044        """Like `signature`, but without the first argument.
1045
1046        This is useful to display constructors.
1047        """
1048        return self.signature.replace(
1049            parameters=list(self.signature.parameters.values())[1:]
1050        )
1051
1052
1053class Variable(Doc[None]):
1054    """
1055    Representation of a variable's documentation. This includes module, class and instance variables.
1056    """
1057
1058    kind = "variable"
1059
1060    default_value: (
1061        Any | empty
1062    )  # technically Any includes empty, but this conveys intent.
1063    """
1064    The variable's default value.
1065    
1066    In some cases, no default value is known. This may either be because a variable is only defined in the constructor,
1067    or it is only declared with a type annotation without assignment (`foo: int`).
1068    To distinguish this case from a default value of `None`, `pdoc.doc_types.empty` is used as a placeholder.
1069    """
1070
1071    annotation: type | empty
1072    """
1073    The variable's type annotation.
1074    
1075    If there is no type annotation, `pdoc.doc_types.empty` is used as a placeholder.
1076    """
1077
1078    def __init__(
1079        self,
1080        modulename: str,
1081        qualname: str,
1082        *,
1083        taken_from: tuple[str, str],
1084        docstring: str,
1085        annotation: type | empty = empty,
1086        default_value: Any | empty = empty,
1087    ):
1088        """
1089        Construct a variable doc object.
1090
1091        While classes and functions can introspect themselves to see their docstring,
1092        variables can't do that as we don't have a "variable object" we could query.
1093        As such, docstring, declaration location, type annotation, and the default value
1094        must be passed manually in the constructor.
1095        """
1096        super().__init__(modulename, qualname, None, taken_from)
1097        # noinspection PyPropertyAccess
1098        self.docstring = inspect.cleandoc(docstring)
1099        self.annotation = annotation
1100        self.default_value = default_value
1101
1102    @cache
1103    @_include_fullname_in_traceback
1104    def __repr__(self):
1105        if self.default_value_str:
1106            default = f" = {self.default_value_str}"
1107        else:
1108            default = ""
1109        return f"<var {self.qualname.rsplit('.')[-1]}{self.annotation_str}{default}{_docstr(self)}>"
1110
1111    @cached_property
1112    def is_classvar(self) -> bool:
1113        """`True` if the variable is a class variable, `False` otherwise."""
1114        if get_origin(self.annotation) is ClassVar:
1115            return True
1116        else:
1117            return False
1118
1119    @cached_property
1120    def is_typevar(self) -> bool:
1121        """`True` if the variable is a `typing.TypeVar`, `False` otherwise."""
1122        if isinstance(self.default_value, TypeVar):
1123            return True
1124        else:
1125            return False
1126
1127    @cached_property
1128    def is_type_alias_type(self) -> bool:
1129        """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise."""
1130        return isinstance(self.default_value, TypeAliasType)
1131
1132    @cached_property
1133    def is_enum_member(self) -> bool:
1134        """`True` if the variable is an enum member, `False` otherwise."""
1135        if isinstance(self.default_value, enum.Enum):
1136            return True
1137        else:
1138            return False
1139
1140    @cached_property
1141    def default_value_str(self) -> str:
1142        """The variable's default value as a pretty-printed str."""
1143        if self.default_value is empty:
1144            return ""
1145        if isinstance(self.default_value, TypeAliasType):
1146            formatted = formatannotation(self.default_value.__value__)
1147            return _remove_collections_abc(formatted)
1148        elif self.annotation == TypeAlias:
1149            formatted = formatannotation(self.default_value)
1150            return _remove_collections_abc(formatted)
1151
1152        # This is not perfect, but a solid attempt at preventing accidental leakage of secrets.
1153        # If you have input on how to improve the heuristic, please send a pull request!
1154        value_taken_from_env_var = (
1155            isinstance(self.default_value, str)
1156            and len(self.default_value) >= 8
1157            and self.default_value in _environ_lookup()
1158        )
1159        if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""):
1160            env_var = "$" + _environ_lookup()[self.default_value]
1161            warnings.warn(
1162                f"The default value of {self.fullname} matches the {env_var} environment variable. "
1163                f"To prevent accidental leakage of secrets, the default value is not displayed. "
1164                f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.",
1165                RuntimeWarning,
1166            )
1167            return env_var
1168
1169        try:
1170            pretty = repr(self.default_value)
1171        except Exception as e:
1172            warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})")
1173            return ""
1174
1175        pretty = _remove_memory_addresses(pretty)
1176        return pretty
1177
1178    @cached_property
1179    def annotation_str(self) -> str:
1180        """The variable's type annotation as a pretty-printed str."""
1181        if self.annotation is not empty:
1182            formatted = formatannotation(self.annotation)
1183            # type aliases don't include the module name in their __repr__, so we add it here.
1184            if isinstance(self.annotation, TypeAliasType):
1185                formatted = f"{self.annotation.__module__}.{formatted}"
1186            return f": {_remove_collections_abc(formatted)}"
1187        else:
1188            return ""
1189
1190
1191@cache
1192def _environ_lookup():
1193    """
1194    A reverse lookup of os.environ. This is a cached function so that it is evaluated lazily.
1195    """
1196    return {value: key for key, value in os.environ.items()}
1197
1198
1199class _PrettySignature(inspect.Signature):
1200    """
1201    A subclass of `inspect.Signature` that pads __str__ over several lines
1202    for complex signatures.
1203    """
1204
1205    MULTILINE_CUTOFF = 70
1206
1207    def _params(self) -> list[str]:
1208        # redeclared here to keep code snipped below as-is.
1209        _POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY
1210        _VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL
1211        _KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY
1212
1213        # https://github.com/python/cpython/blob/799f8489d418b7f9207d333eac38214931bd7dcc/Lib/inspect.py#L3083-L3117
1214        # Change: added re.sub() to formatted = ....
1215        # ✂ start ✂
1216        result = []
1217        render_pos_only_separator = False
1218        render_kw_only_separator = True
1219        for param in self.parameters.values():
1220            formatted = str(param)
1221            formatted = _remove_memory_addresses(formatted)
1222            formatted = _remove_collections_abc(formatted)
1223
1224            kind = param.kind
1225
1226            if kind == _POSITIONAL_ONLY:
1227                render_pos_only_separator = True
1228            elif render_pos_only_separator:
1229                # It's not a positional-only parameter, and the flag
1230                # is set to 'True' (there were pos-only params before.)
1231                result.append("/")
1232                render_pos_only_separator = False
1233
1234            if kind == _VAR_POSITIONAL:
1235                # OK, we have an '*args'-like parameter, so we won't need
1236                # a '*' to separate keyword-only arguments
1237                render_kw_only_separator = False
1238            elif kind == _KEYWORD_ONLY and render_kw_only_separator:
1239                # We have a keyword-only parameter to render and we haven't
1240                # rendered an '*args'-like parameter before, so add a '*'
1241                # separator to the parameters list ("foo(arg1, *, arg2)" case)
1242                result.append("*")
1243                # This condition should be only triggered once, so
1244                # reset the flag
1245                render_kw_only_separator = False
1246
1247            result.append(formatted)
1248
1249        if render_pos_only_separator:
1250            # There were only positional-only parameters, hence the
1251            # flag was not reset to 'False'
1252            result.append("/")
1253        # ✂ end ✂
1254
1255        return result
1256
1257    def _return_annotation_str(self) -> str:
1258        if self.return_annotation is not empty:
1259            formatted = formatannotation(self.return_annotation)
1260            return _remove_collections_abc(formatted)
1261        else:
1262            return ""
1263
1264    def __str__(self):
1265        result = self._params()
1266        return_annot = self._return_annotation_str()
1267
1268        total_len = sum(len(x) + 2 for x in result) + len(return_annot)
1269
1270        if total_len > self.MULTILINE_CUTOFF:
1271            rendered = "(\n    " + ",\n    ".join(result) + "\n)"
1272        else:
1273            rendered = "({})".format(", ".join(result))
1274        if return_annot:
1275            rendered += f" -> {return_annot}"
1276
1277        return rendered
1278
1279
1280def _cut(x: str) -> str:
1281    """helper function for Doc.__repr__()"""
1282    if len(x) < 20:
1283        return x
1284    else:
1285        return x[:20] + "…"
1286
1287
1288def _docstr(doc: Doc) -> str:
1289    """helper function for Doc.__repr__()"""
1290    docstr = []
1291    if doc.is_inherited:
1292        docstr.append(f"inherited from {'.'.join(doc.taken_from).rstrip('.')}")
1293    if doc.docstring:
1294        docstr.append(_cut(doc.docstring))
1295    if docstr:
1296        return f"  # {', '.join(docstr)}"
1297    else:
1298        return ""
1299
1300
1301def _decorators(doc: Class | Function) -> str:
1302    """helper function for Doc.__repr__()"""
1303    if doc.decorators:
1304        return " ".join(doc.decorators) + " "
1305    else:
1306        return ""
1307
1308
1309def _children(doc: Namespace) -> str:
1310    children = "\n".join(
1311        repr(x)
1312        for x in doc.members.values()
1313        if not x.name.startswith("_") or x.name == "__init__"
1314    )
1315    if children:
1316        children += "\n"
1317        children = f"\n{textwrap.indent(children, '    ')}"
1318    return children
1319
1320
1321def _safe_getattr(obj, attr, default):
1322    """Like `getattr()`, but never raises."""
1323    try:
1324        return getattr(obj, attr, default)
1325    except Exception as e:
1326        warnings.warn(
1327            f"getattr({obj!r}, {attr!r}, {default!r}) raised an exception: {e!r}"
1328        )
1329        return default
1330
1331
1332def _safe_getdoc(obj: Any) -> str:
1333    """Like `inspect.getdoc()`, but never raises. Always returns a stripped string."""
1334    try:
1335        doc = inspect.getdoc(obj) or ""
1336    except Exception as e:
1337        warnings.warn(f"inspect.getdoc({obj!r}) raised an exception: {e!r}")
1338        return ""
1339    else:
1340        return doc.strip()
1341
1342
1343_Enum_default_docstrings = tuple(
1344    {
1345        _safe_getdoc(enum.Enum),
1346        _safe_getdoc(enum.IntEnum),
1347        _safe_getdoc(_safe_getattr(enum, "StrEnum", enum.Enum)),
1348    }
1349)
1350
1351
1352def _remove_memory_addresses(x: str) -> str:
1353    """Remove memory addresses from repr() output"""
1354    return re.sub(r" at 0x[0-9a-fA-F]+(?=>)", "", x)
1355
1356
1357def _remove_collections_abc(x: str) -> str:
1358    """Remove 'collections.abc' from type signatures."""
1359    return re.sub(r"(?!\.)\bcollections\.abc\.", "", x)
class Doc(typing.Generic[~T]):
 83class Doc(Generic[T]):
 84    """
 85    A base class for all documentation objects.
 86    """
 87
 88    modulename: str
 89    """
 90    The module that this object is in, for example `pdoc.doc`.
 91    """
 92
 93    qualname: str
 94    """
 95    The qualified identifier name for this object. For example, if we have the following code:
 96    
 97    ```python
 98    class Foo:
 99        def bar(self):
100            pass
101    ```
102    
103    The qualname of `Foo`'s `bar` method is `Foo.bar`. The qualname of the `Foo` class is just `Foo`.
104    
105    See <https://www.python.org/dev/peps/pep-3155/> for details.
106    """
107
108    obj: T
109    """
110    The underlying Python object.
111    """
112
113    taken_from: tuple[str, str]
114    """
115    `(modulename, qualname)` of this doc object's original location.
116    In the context of a module, this points to the location it was imported from,
117    in the context of classes, this points to the class an attribute is inherited from.
118    """
119
120    kind: ClassVar[str]
121    """
122    The type of the doc object, either `"module"`, `"class"`, `"function"`, or `"variable"`.
123    """
124
125    @property
126    def type(self) -> str:  # pragma: no cover
127        warnings.warn(
128            "pdoc.doc.Doc.type is deprecated. Use pdoc.doc.Doc.kind instead.",
129            DeprecationWarning,
130        )
131        return self.kind
132
133    def __init__(
134        self, modulename: str, qualname: str, obj: T, taken_from: tuple[str, str]
135    ):
136        """
137        Initializes a documentation object, where
138        `modulename` is the name this module is defined in,
139        `qualname` contains a dotted path leading to the object from the module top-level, and
140        `obj` is the object to document.
141        """
142        self.modulename = modulename
143        self.qualname = qualname
144        self.obj = obj
145        self.taken_from = taken_from
146
147    @cached_property
148    def fullname(self) -> str:
149        """The full qualified name of this doc object, for example `pdoc.doc.Doc`."""
150        # qualname is empty for modules
151        return f"{self.modulename}.{self.qualname}".rstrip(".")
152
153    @cached_property
154    def name(self) -> str:
155        """The name of this object. For top-level functions and classes, this is equal to the qualname attribute."""
156        return self.fullname.split(".")[-1]
157
158    @cached_property
159    def docstring(self) -> str:
160        """
161        The docstring for this object. It has already been cleaned by `inspect.cleandoc`.
162
163        If no docstring can be found, an empty string is returned.
164        """
165        return _safe_getdoc(self.obj)
166
167    @cached_property
168    def source(self) -> str:
169        """
170        The source code of the Python object as a `str`.
171
172        If the source cannot be obtained (for example, because we are dealing with a native C object),
173        an empty string is returned.
174        """
175        return doc_ast.get_source(self.obj)
176
177    @cached_property
178    def source_file(self) -> Path | None:
179        """The name of the Python source file in which this object was defined. `None` for built-in objects."""
180        try:
181            return Path(inspect.getsourcefile(self.obj) or inspect.getfile(self.obj))  # type: ignore
182        except TypeError:
183            return None
184
185    @cached_property
186    def source_lines(self) -> tuple[int, int] | None:
187        """
188        Return a `(start, end)` line number tuple for this object.
189
190        If no source file can be found, `None` is returned.
191        """
192        try:
193            lines, start = inspect.getsourcelines(self.obj)  # type: ignore
194            return start, start + len(lines) - 1
195        except Exception:
196            return None
197
198    @cached_property
199    def is_inherited(self) -> bool:
200        """
201        If True, the doc object is inherited from another location.
202        This most commonly refers to methods inherited by a subclass,
203        but can also apply to variables that are assigned a class defined
204        in a different module.
205        """
206        return (self.modulename, self.qualname) != self.taken_from
207
208    def __lt__(self, other):
209        assert isinstance(other, Doc)
210        return self.fullname.replace("__init__", "").__lt__(
211            other.fullname.replace("__init__", "")
212        )

A base class for all documentation objects.

Doc(modulename: str, qualname: str, obj: ~T, taken_from: tuple[str, str])
133    def __init__(
134        self, modulename: str, qualname: str, obj: T, taken_from: tuple[str, str]
135    ):
136        """
137        Initializes a documentation object, where
138        `modulename` is the name this module is defined in,
139        `qualname` contains a dotted path leading to the object from the module top-level, and
140        `obj` is the object to document.
141        """
142        self.modulename = modulename
143        self.qualname = qualname
144        self.obj = obj
145        self.taken_from = taken_from

Initializes a documentation object, where modulename is the name this module is defined in, qualname contains a dotted path leading to the object from the module top-level, and obj is the object to document.

modulename: str

The module that this object is in, for example pdoc.doc.

qualname: str

The qualified identifier name for this object. For example, if we have the following code:

class Foo:
    def bar(self):
        pass

The qualname of Foo's bar method is Foo.bar. The qualname of the Foo class is just Foo.

See https://www.python.org/dev/peps/pep-3155/ for details.

obj: ~T

The underlying Python object.

taken_from: tuple[str, str]

(modulename, qualname) of this doc object's original location. In the context of a module, this points to the location it was imported from, in the context of classes, this points to the class an attribute is inherited from.

kind: ClassVar[str]

The type of the doc object, either "module", "class", "function", or "variable".

type: str
125    @property
126    def type(self) -> str:  # pragma: no cover
127        warnings.warn(
128            "pdoc.doc.Doc.type is deprecated. Use pdoc.doc.Doc.kind instead.",
129            DeprecationWarning,
130        )
131        return self.kind
fullname: str
147    @cached_property
148    def fullname(self) -> str:
149        """The full qualified name of this doc object, for example `pdoc.doc.Doc`."""
150        # qualname is empty for modules
151        return f"{self.modulename}.{self.qualname}".rstrip(".")

The full qualified name of this doc object, for example pdoc.doc.Doc.

name: str
153    @cached_property
154    def name(self) -> str:
155        """The name of this object. For top-level functions and classes, this is equal to the qualname attribute."""
156        return self.fullname.split(".")[-1]

The name of this object. For top-level functions and classes, this is equal to the qualname attribute.

docstring: str
158    @cached_property
159    def docstring(self) -> str:
160        """
161        The docstring for this object. It has already been cleaned by `inspect.cleandoc`.
162
163        If no docstring can be found, an empty string is returned.
164        """
165        return _safe_getdoc(self.obj)

The docstring for this object. It has already been cleaned by inspect.cleandoc.

If no docstring can be found, an empty string is returned.

source: str
167    @cached_property
168    def source(self) -> str:
169        """
170        The source code of the Python object as a `str`.
171
172        If the source cannot be obtained (for example, because we are dealing with a native C object),
173        an empty string is returned.
174        """
175        return doc_ast.get_source(self.obj)

The source code of the Python object as a str.

If the source cannot be obtained (for example, because we are dealing with a native C object), an empty string is returned.

source_file: pathlib.Path | None
177    @cached_property
178    def source_file(self) -> Path | None:
179        """The name of the Python source file in which this object was defined. `None` for built-in objects."""
180        try:
181            return Path(inspect.getsourcefile(self.obj) or inspect.getfile(self.obj))  # type: ignore
182        except TypeError:
183            return None

The name of the Python source file in which this object was defined. None for built-in objects.

source_lines: tuple[int, int] | None
185    @cached_property
186    def source_lines(self) -> tuple[int, int] | None:
187        """
188        Return a `(start, end)` line number tuple for this object.
189
190        If no source file can be found, `None` is returned.
191        """
192        try:
193            lines, start = inspect.getsourcelines(self.obj)  # type: ignore
194            return start, start + len(lines) - 1
195        except Exception:
196            return None

Return a (start, end) line number tuple for this object.

If no source file can be found, None is returned.

is_inherited: bool
198    @cached_property
199    def is_inherited(self) -> bool:
200        """
201        If True, the doc object is inherited from another location.
202        This most commonly refers to methods inherited by a subclass,
203        but can also apply to variables that are assigned a class defined
204        in a different module.
205        """
206        return (self.modulename, self.qualname) != self.taken_from

If True, the doc object is inherited from another location. This most commonly refers to methods inherited by a subclass, but can also apply to variables that are assigned a class defined in a different module.

class Namespace(pdoc.doc.Doc[~U]):
218class Namespace(Doc[U], metaclass=ABCMeta):
219    """
220    A documentation object that can have children. In other words, either a module or a class.
221    """
222
223    @cached_property
224    @abstractmethod
225    def _member_objects(self) -> dict[str, Any]:
226        """
227        A mapping from *all* public and private member names to their Python objects.
228        """
229
230    @cached_property
231    @abstractmethod
232    def _var_docstrings(self) -> dict[str, str]:
233        """A mapping from some member variable names to their docstrings."""
234
235    @cached_property
236    @abstractmethod
237    def _func_docstrings(self) -> dict[str, str]:
238        """A mapping from some member function names to their raw (not processed by any @decorators) docstrings."""
239
240    @cached_property
241    @abstractmethod
242    def _var_annotations(self) -> dict[str, Any]:
243        """A mapping from some member variable names to their type annotations."""
244
245    @abstractmethod
246    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
247        """The location this member was taken from. If unknown, (modulename, qualname) is returned."""
248
249    @cached_property
250    @abstractmethod
251    def own_members(self) -> list[Doc]:
252        """A list of all own (i.e. non-inherited) `members`."""
253
254    @cached_property
255    def members(self) -> dict[str, Doc]:
256        """A mapping from all members to their documentation objects.
257
258        This mapping includes private members; they are only filtered out as part of the template logic.
259        Constructors for enums, dicts, and abstract base classes are not picked up unless they have a custom docstring.
260        """
261        members: dict[str, Doc] = {}
262        for name, obj in self._member_objects.items():
263            qualname = f"{self.qualname}.{name}".lstrip(".")
264            taken_from = self._taken_from(name, obj)
265            doc: Doc[Any]
266
267            is_classmethod = isinstance(obj, classmethod)
268            is_property = (
269                isinstance(obj, (property, cached_property))
270                or
271                # Python 3.9 - 3.10: @classmethod @property is allowed.
272                is_classmethod
273                and isinstance(obj.__func__, (property, cached_property))
274            )
275            if is_property:
276                func = obj
277                if is_classmethod:
278                    func = obj.__func__
279                if isinstance(func, property):
280                    func = func.fget
281                else:
282                    assert isinstance(func, cached_property)
283                    func = func.func
284
285                doc_f = Function(self.modulename, qualname, func, taken_from)
286                doc = Variable(
287                    self.modulename,
288                    qualname,
289                    docstring=doc_f.docstring,
290                    annotation=doc_f.signature.return_annotation,
291                    default_value=empty,
292                    taken_from=taken_from,
293                )
294                doc.source = doc_f.source
295                doc.source_file = doc_f.source_file
296                doc.source_lines = doc_f.source_lines
297            elif inspect.isroutine(obj):
298                doc = Function(self.modulename, qualname, obj, taken_from)  # type: ignore
299            elif (
300                inspect.isclass(obj)
301                and obj is not empty
302                and not isinstance(obj, GenericAlias)
303                and obj.__qualname__.rpartition(".")[2] == qualname.rpartition(".")[2]
304            ):
305                # `dict[str,str]` is a GenericAlias instance. We want to render type aliases as variables though.
306                doc = Class(self.modulename, qualname, obj, taken_from)
307            elif inspect.ismodule(obj):
308                if os.environ.get("PDOC_SUBMODULES"):  # pragma: no cover
309                    doc = Module.from_name(obj.__name__)
310                else:
311                    continue
312            elif inspect.isdatadescriptor(obj):
313                doc = Variable(
314                    self.modulename,
315                    qualname,
316                    docstring=getattr(obj, "__doc__", None) or "",
317                    annotation=self._var_annotations.get(name, empty),
318                    default_value=empty,
319                    taken_from=taken_from,
320                )
321            else:
322                doc = Variable(
323                    self.modulename,
324                    qualname,
325                    docstring="",
326                    annotation=self._var_annotations.get(name, empty),
327                    default_value=_pydantic.default_value(self.obj, name, obj),
328                    taken_from=taken_from,
329                )
330
331            if _doc := _pydantic.get_field_docstring(cast(type, self.obj), name):
332                doc.docstring = _doc
333            elif self._var_docstrings.get(name):
334                doc.docstring = self._var_docstrings[name]
335            elif self._func_docstrings.get(name) and not doc.docstring:
336                doc.docstring = self._func_docstrings[name]
337
338            members[doc.name] = doc
339
340        if isinstance(self, Module):
341            # quirk: doc_pyi expects .members to be set already
342            self.members = members  # type: ignore
343            doc_pyi.include_typeinfo_from_stub_files(self)
344
345        return members
346
347    @cached_property
348    def _members_by_origin(self) -> dict[tuple[str, str], list[Doc]]:
349        """A mapping from (modulename, qualname) locations to the attributes taken from that path"""
350        locations: dict[tuple[str, str], list[Doc]] = {}
351        for member in self.members.values():
352            mod, qualname = member.taken_from
353            parent_qualname = ".".join(qualname.rsplit(".", maxsplit=1)[:-1])
354            locations.setdefault((mod, parent_qualname), [])
355            locations[(mod, parent_qualname)].append(member)
356        return locations
357
358    @cached_property
359    def inherited_members(self) -> dict[tuple[str, str], list[Doc]]:
360        """A mapping from (modulename, qualname) locations to the attributes inherited from that path"""
361        return {
362            k: v
363            for k, v in self._members_by_origin.items()
364            if k not in (self.taken_from, (self.modulename, self.qualname))
365        }
366
367    @cached_property
368    def flattened_own_members(self) -> list[Doc]:
369        """
370        A list of all documented members and their child classes, recursively.
371        """
372        flattened = []
373        for x in self.own_members:
374            flattened.append(x)
375            if isinstance(x, Class):
376                flattened.extend(
377                    [cls for cls in x.flattened_own_members if isinstance(cls, Class)]
378                )
379        return flattened
380
381    @cache
382    def get(self, identifier: str) -> Doc | None:
383        """Returns the documentation object for a particular identifier, or `None` if the identifier cannot be found."""
384        head, _, tail = identifier.partition(".")
385        if tail:
386            h = self.members.get(head, None)
387            if isinstance(h, Class):
388                return h.get(tail)
389            return None
390        else:
391            return self.members.get(identifier, None)

A documentation object that can have children. In other words, either a module or a class.

own_members: list[Doc]
249    @cached_property
250    @abstractmethod
251    def own_members(self) -> list[Doc]:
252        """A list of all own (i.e. non-inherited) `members`."""

A list of all own (i.e. non-inherited) members.

members: dict[str, Doc]
254    @cached_property
255    def members(self) -> dict[str, Doc]:
256        """A mapping from all members to their documentation objects.
257
258        This mapping includes private members; they are only filtered out as part of the template logic.
259        Constructors for enums, dicts, and abstract base classes are not picked up unless they have a custom docstring.
260        """
261        members: dict[str, Doc] = {}
262        for name, obj in self._member_objects.items():
263            qualname = f"{self.qualname}.{name}".lstrip(".")
264            taken_from = self._taken_from(name, obj)
265            doc: Doc[Any]
266
267            is_classmethod = isinstance(obj, classmethod)
268            is_property = (
269                isinstance(obj, (property, cached_property))
270                or
271                # Python 3.9 - 3.10: @classmethod @property is allowed.
272                is_classmethod
273                and isinstance(obj.__func__, (property, cached_property))
274            )
275            if is_property:
276                func = obj
277                if is_classmethod:
278                    func = obj.__func__
279                if isinstance(func, property):
280                    func = func.fget
281                else:
282                    assert isinstance(func, cached_property)
283                    func = func.func
284
285                doc_f = Function(self.modulename, qualname, func, taken_from)
286                doc = Variable(
287                    self.modulename,
288                    qualname,
289                    docstring=doc_f.docstring,
290                    annotation=doc_f.signature.return_annotation,
291                    default_value=empty,
292                    taken_from=taken_from,
293                )
294                doc.source = doc_f.source
295                doc.source_file = doc_f.source_file
296                doc.source_lines = doc_f.source_lines
297            elif inspect.isroutine(obj):
298                doc = Function(self.modulename, qualname, obj, taken_from)  # type: ignore
299            elif (
300                inspect.isclass(obj)
301                and obj is not empty
302                and not isinstance(obj, GenericAlias)
303                and obj.__qualname__.rpartition(".")[2] == qualname.rpartition(".")[2]
304            ):
305                # `dict[str,str]` is a GenericAlias instance. We want to render type aliases as variables though.
306                doc = Class(self.modulename, qualname, obj, taken_from)
307            elif inspect.ismodule(obj):
308                if os.environ.get("PDOC_SUBMODULES"):  # pragma: no cover
309                    doc = Module.from_name(obj.__name__)
310                else:
311                    continue
312            elif inspect.isdatadescriptor(obj):
313                doc = Variable(
314                    self.modulename,
315                    qualname,
316                    docstring=getattr(obj, "__doc__", None) or "",
317                    annotation=self._var_annotations.get(name, empty),
318                    default_value=empty,
319                    taken_from=taken_from,
320                )
321            else:
322                doc = Variable(
323                    self.modulename,
324                    qualname,
325                    docstring="",
326                    annotation=self._var_annotations.get(name, empty),
327                    default_value=_pydantic.default_value(self.obj, name, obj),
328                    taken_from=taken_from,
329                )
330
331            if _doc := _pydantic.get_field_docstring(cast(type, self.obj), name):
332                doc.docstring = _doc
333            elif self._var_docstrings.get(name):
334                doc.docstring = self._var_docstrings[name]
335            elif self._func_docstrings.get(name) and not doc.docstring:
336                doc.docstring = self._func_docstrings[name]
337
338            members[doc.name] = doc
339
340        if isinstance(self, Module):
341            # quirk: doc_pyi expects .members to be set already
342            self.members = members  # type: ignore
343            doc_pyi.include_typeinfo_from_stub_files(self)
344
345        return members

A mapping from all members to their documentation objects.

This mapping includes private members; they are only filtered out as part of the template logic. Constructors for enums, dicts, and abstract base classes are not picked up unless they have a custom docstring.

inherited_members: dict[tuple[str, str], list[Doc]]
358    @cached_property
359    def inherited_members(self) -> dict[tuple[str, str], list[Doc]]:
360        """A mapping from (modulename, qualname) locations to the attributes inherited from that path"""
361        return {
362            k: v
363            for k, v in self._members_by_origin.items()
364            if k not in (self.taken_from, (self.modulename, self.qualname))
365        }

A mapping from (modulename, qualname) locations to the attributes inherited from that path

flattened_own_members: list[Doc]
367    @cached_property
368    def flattened_own_members(self) -> list[Doc]:
369        """
370        A list of all documented members and their child classes, recursively.
371        """
372        flattened = []
373        for x in self.own_members:
374            flattened.append(x)
375            if isinstance(x, Class):
376                flattened.extend(
377                    [cls for cls in x.flattened_own_members if isinstance(cls, Class)]
378                )
379        return flattened

A list of all documented members and their child classes, recursively.

@cache
def get(self, identifier: str) -> Doc | None:
381    @cache
382    def get(self, identifier: str) -> Doc | None:
383        """Returns the documentation object for a particular identifier, or `None` if the identifier cannot be found."""
384        head, _, tail = identifier.partition(".")
385        if tail:
386            h = self.members.get(head, None)
387            if isinstance(h, Class):
388                return h.get(tail)
389            return None
390        else:
391            return self.members.get(identifier, None)

Returns the documentation object for a particular identifier, or None if the identifier cannot be found.

class Module(pdoc.doc.Namespace[module]):
394class Module(Namespace[types.ModuleType]):
395    """
396    Representation of a module's documentation.
397    """
398
399    def __init__(
400        self,
401        module: types.ModuleType,
402    ):
403        """
404        Creates a documentation object given the actual
405        Python module object.
406        """
407        super().__init__(module.__name__, "", module, (module.__name__, ""))
408
409    kind = "module"
410
411    @classmethod
412    @cache
413    def from_name(cls, name: str) -> Module:
414        """Create a `Module` object by supplying the module's (full) name."""
415        return cls(extract.load_module(name))
416
417    @cache
418    @_include_fullname_in_traceback
419    def __repr__(self):
420        return f"<module {self.fullname}{_docstr(self)}{_children(self)}>"
421
422    @cached_property
423    def is_package(self) -> bool:
424        """
425        `True` if the module is a package, `False` otherwise.
426
427        Packages are a special kind of module that may have submodules.
428        Typically, this means that this file is in a directory named like the
429        module with the name `__init__.py`.
430        """
431        return _safe_getattr(self.obj, "__path__", None) is not None
432
433    @cached_property
434    def _var_docstrings(self) -> dict[str, str]:
435        return doc_ast.walk_tree(self.obj).var_docstrings
436
437    @cached_property
438    def _func_docstrings(self) -> dict[str, str]:
439        return doc_ast.walk_tree(self.obj).func_docstrings
440
441    @cached_property
442    def _var_annotations(self) -> dict[str, Any]:
443        annotations = doc_ast.walk_tree(self.obj).annotations.copy()
444        for k, v in _safe_getattr(self.obj, "__annotations__", {}).items():
445            annotations[k] = v
446
447        return resolve_annotations(annotations, self.obj, None, self.fullname)
448
449    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
450        if obj is empty:
451            return self.modulename, f"{self.qualname}.{member_name}".lstrip(".")
452        if isinstance(obj, types.ModuleType):
453            return obj.__name__, ""
454
455        mod = _safe_getattr(obj, "__module__", None)
456        qual = _safe_getattr(obj, "__qualname__", None)
457        if mod and isinstance(qual, str) and "<locals>" not in qual:
458            return mod, qual
459        else:
460            # This might be wrong, but it's the best guess we have.
461            return (mod or self.modulename), f"{self.qualname}.{member_name}".lstrip(
462                "."
463            )
464
465    @cached_property
466    def own_members(self) -> list[Doc]:
467        return list(self.members.values())
468
469    @cached_property
470    def submodules(self) -> list[Module]:
471        """A list of all (direct) submodules."""
472        include: Callable[[str], bool]
473        mod_all = _safe_getattr(self.obj, "__all__", False)
474        if mod_all is not False:
475            mod_all_pos = {name: i for i, name in enumerate(mod_all)}
476            include = mod_all_pos.__contains__
477        else:
478
479            def include(name: str) -> bool:
480                # optimization: we don't even try to load modules starting with an underscore as they would not be
481                # visible by default. The downside of this is that someone who overrides `is_public` will miss those
482                # entries, the upsides are 1) better performance and 2) less warnings because of import failures
483                # (think of OS-specific modules, e.g. _linux.py failing to import on Windows).
484                return not name.startswith("_")
485
486        submodules: list[Module] = []
487        for mod_name, mod in extract.iter_modules2(self.obj).items():
488            if not include(mod_name):
489                continue
490            try:
491                module = Module.from_name(mod.name)
492            except RuntimeError:
493                warnings.warn(f"Couldn't import {mod.name}:\n{traceback.format_exc()}")
494                continue
495            submodules.append(module)
496
497        if mod_all:
498            submodules = sorted(submodules, key=lambda m: mod_all_pos[m.name])
499
500        return submodules
501
502    @cached_property
503    def _ast_keys(self) -> set[str]:
504        return (
505            self._var_docstrings.keys()
506            | self._func_docstrings.keys()
507            | self._var_annotations.keys()
508        )
509
510    @cached_property
511    def _member_objects(self) -> dict[str, Any]:
512        members = {}
513
514        all_: list[str] | None = _safe_getattr(self.obj, "__all__", None)
515        if all_ is not None:
516            for name in all_:
517                if not isinstance(name, str):
518                    # Gracefully handle the case where objects are directly specified in __all__.
519                    name = _safe_getattr(name, "__name__", str(name))
520                if name in self.obj.__dict__:
521                    val = self.obj.__dict__[name]
522                elif name in self._var_annotations:
523                    val = empty
524                else:
525                    # this may be an unimported submodule, try importing.
526                    # (https://docs.python.org/3/tutorial/modules.html#importing-from-a-package)
527                    try:
528                        val = extract.load_module(f"{self.modulename}.{name}")
529                    except RuntimeError as e:
530                        warnings.warn(
531                            f"Found {name!r} in {self.modulename}.__all__, but it does not resolve: {e}"
532                        )
533                        val = empty
534                members[name] = val
535
536        else:
537            # Starting with Python 3.10, __annotations__ is created on demand,
538            # so we make a copy here as obj.__dict__ is changed while we iterate over it.
539            # Additionally, accessing self._ast_keys may lead to the execution of TYPE_CHECKING blocks,
540            # which may also modify obj.__dict__. (https://github.com/mitmproxy/pdoc/issues/351)
541            for name, obj in list(self.obj.__dict__.items()):
542                # We already exclude everything here that is imported.
543                obj_module = inspect.getmodule(obj)
544                declared_in_this_module = self.obj.__name__ == _safe_getattr(
545                    obj_module, "__name__", None
546                )
547                include_in_docs = declared_in_this_module or name in self._ast_keys
548                if include_in_docs:
549                    members[name] = obj
550
551            for name in self._var_docstrings:
552                members.setdefault(name, empty)
553            for name in self._var_annotations:
554                members.setdefault(name, empty)
555
556            members, notfound = doc_ast.sort_by_source(self.obj, {}, members)
557            members.update(notfound)
558
559        return members
560
561    @cached_property
562    def variables(self) -> list[Variable]:
563        """
564        A list of all documented module level variables.
565        """
566        return [x for x in self.members.values() if isinstance(x, Variable)]
567
568    @cached_property
569    def classes(self) -> list[Class]:
570        """
571        A list of all documented module level classes.
572        """
573        return [x for x in self.members.values() if isinstance(x, Class)]
574
575    @cached_property
576    def functions(self) -> list[Function]:
577        """
578        A list of all documented module level functions.
579        """
580        return [x for x in self.members.values() if isinstance(x, Function)]

Representation of a module's documentation.

Module(module: module)
399    def __init__(
400        self,
401        module: types.ModuleType,
402    ):
403        """
404        Creates a documentation object given the actual
405        Python module object.
406        """
407        super().__init__(module.__name__, "", module, (module.__name__, ""))

Creates a documentation object given the actual Python module object.

kind = 'module'

The type of the doc object, either "module", "class", "function", or "variable".

@classmethod
@cache
def from_name(cls, name: str) -> Module:
411    @classmethod
412    @cache
413    def from_name(cls, name: str) -> Module:
414        """Create a `Module` object by supplying the module's (full) name."""
415        return cls(extract.load_module(name))

Create a Module object by supplying the module's (full) name.

is_package: bool
422    @cached_property
423    def is_package(self) -> bool:
424        """
425        `True` if the module is a package, `False` otherwise.
426
427        Packages are a special kind of module that may have submodules.
428        Typically, this means that this file is in a directory named like the
429        module with the name `__init__.py`.
430        """
431        return _safe_getattr(self.obj, "__path__", None) is not None

True if the module is a package, False otherwise.

Packages are a special kind of module that may have submodules. Typically, this means that this file is in a directory named like the module with the name __init__.py.

own_members: list[Doc]
465    @cached_property
466    def own_members(self) -> list[Doc]:
467        return list(self.members.values())

A list of all own (i.e. non-inherited) members.

submodules: list[Module]
469    @cached_property
470    def submodules(self) -> list[Module]:
471        """A list of all (direct) submodules."""
472        include: Callable[[str], bool]
473        mod_all = _safe_getattr(self.obj, "__all__", False)
474        if mod_all is not False:
475            mod_all_pos = {name: i for i, name in enumerate(mod_all)}
476            include = mod_all_pos.__contains__
477        else:
478
479            def include(name: str) -> bool:
480                # optimization: we don't even try to load modules starting with an underscore as they would not be
481                # visible by default. The downside of this is that someone who overrides `is_public` will miss those
482                # entries, the upsides are 1) better performance and 2) less warnings because of import failures
483                # (think of OS-specific modules, e.g. _linux.py failing to import on Windows).
484                return not name.startswith("_")
485
486        submodules: list[Module] = []
487        for mod_name, mod in extract.iter_modules2(self.obj).items():
488            if not include(mod_name):
489                continue
490            try:
491                module = Module.from_name(mod.name)
492            except RuntimeError:
493                warnings.warn(f"Couldn't import {mod.name}:\n{traceback.format_exc()}")
494                continue
495            submodules.append(module)
496
497        if mod_all:
498            submodules = sorted(submodules, key=lambda m: mod_all_pos[m.name])
499
500        return submodules

A list of all (direct) submodules.

variables: list[Variable]
561    @cached_property
562    def variables(self) -> list[Variable]:
563        """
564        A list of all documented module level variables.
565        """
566        return [x for x in self.members.values() if isinstance(x, Variable)]

A list of all documented module level variables.

classes: list[Class]
568    @cached_property
569    def classes(self) -> list[Class]:
570        """
571        A list of all documented module level classes.
572        """
573        return [x for x in self.members.values() if isinstance(x, Class)]

A list of all documented module level classes.

functions: list[Function]
575    @cached_property
576    def functions(self) -> list[Function]:
577        """
578        A list of all documented module level functions.
579        """
580        return [x for x in self.members.values() if isinstance(x, Function)]

A list of all documented module level functions.

class Class(pdoc.doc.Namespace[type]):
583class Class(Namespace[type]):
584    """
585    Representation of a class's documentation.
586    """
587
588    kind = "class"
589
590    @cache
591    @_include_fullname_in_traceback
592    def __repr__(self):
593        return f"<{_decorators(self)}class {self.modulename}.{self.qualname}{_docstr(self)}{_children(self)}>"
594
595    @cached_property
596    def docstring(self) -> str:
597        doc = Doc.docstring.__get__(self)  # type: ignore
598        if doc == dict.__doc__:
599            # Don't display default docstring for dict subclasses (primarily TypedDict).
600            return ""
601        if doc in _Enum_default_docstrings:
602            # Don't display default docstring for enum subclasses.
603            return ""
604        if dataclasses.is_dataclass(self.obj) and doc.startswith(self.obj.__name__):
605            try:
606                sig = inspect.signature(self.obj)
607            except Exception:
608                pass
609            else:
610                # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py
611                is_dataclass_with_default_docstring = doc == self.obj.__name__ + str(
612                    sig
613                ).replace(" -> None", "")
614                if is_dataclass_with_default_docstring:
615                    return ""
616        return doc
617
618    @cached_property
619    def _var_docstrings(self) -> dict[str, str]:
620        docstrings: dict[str, str] = {}
621        for cls in self._bases:
622            for name, docstr in doc_ast.walk_tree(cls).var_docstrings.items():
623                docstrings.setdefault(name, docstr)
624        return docstrings
625
626    @cached_property
627    def _func_docstrings(self) -> dict[str, str]:
628        docstrings: dict[str, str] = {}
629        for cls in self._bases:
630            for name, docstr in doc_ast.walk_tree(cls).func_docstrings.items():
631                docstrings.setdefault(name, docstr)
632        return docstrings
633
634    @cached_property
635    def _var_annotations(self) -> dict[str, type]:
636        # this is a bit tricky: __annotations__ also includes annotations from parent classes,
637        # but we need to execute them in the namespace of the parent class.
638        # Our workaround for this is to walk the MRO backwards, and only update/evaluate only if the annotation changes.
639        annotations: dict[
640            str, tuple[Any, type]
641        ] = {}  # attribute -> (annotation_unresolved, annotation_resolved)
642        for cls in reversed(self._bases):
643            cls_annotations = doc_ast.walk_tree(cls).annotations.copy()
644            dynamic_annotations = _safe_getattr(cls, "__annotations__", None)
645            if isinstance(dynamic_annotations, dict):
646                for attr, unresolved_annotation in dynamic_annotations.items():
647                    cls_annotations[attr] = unresolved_annotation
648            cls_fullname = (
649                (_safe_getattr(cls, "__module__", "") or "") + "." + cls.__qualname__
650            ).lstrip(".")
651
652            new_annotations = {
653                attr: unresolved_annotation
654                for attr, unresolved_annotation in cls_annotations.items()
655                if attr not in annotations
656                or annotations[attr][0] is not unresolved_annotation
657            }
658            localns = _safe_getattr(cls, "__dict__", None)
659            for attr, t in resolve_annotations(
660                new_annotations, inspect.getmodule(cls), localns, cls_fullname
661            ).items():
662                annotations[attr] = (new_annotations[attr], t)
663
664        return {k: v[1] for k, v in annotations.items()}
665
666    @cached_property
667    def _bases(self) -> tuple[type, ...]:
668        orig_bases = _safe_getattr(self.obj, "__orig_bases__", ())
669
670        if is_typeddict(self.obj):
671            if sys.version_info < (3, 12):  # pragma: no cover
672                # TypedDicts on Python <3.12 have a botched __mro__. We need to fix it.
673                return (self.obj, *orig_bases[:-1])
674            else:
675                # TypedDict on Python >=3.12 removes intermediate classes from __mro__,
676                # so we use orig_bases to recover the full mro.
677                while orig_bases and orig_bases[-1] is not TypedDict:
678                    parent_bases = _safe_getattr(orig_bases[-1], "__orig_bases__", ())
679                    if (
680                        len(parent_bases) != 1 or parent_bases in orig_bases
681                    ):  # sanity check that things look right
682                        break  # pragma: no cover
683                    orig_bases = (*orig_bases, parent_bases[0])
684
685        # __mro__ and __orig_bases__ differ between Python versions and special cases like TypedDict/NamedTuple.
686        # This here is a pragmatic approximation of what we want.
687        return (
688            *(base for base in orig_bases if isinstance(base, type)),
689            *self.obj.__mro__,
690        )
691
692    @cached_property
693    def _declarations(self) -> dict[str, tuple[str, str]]:
694        decls: dict[str, tuple[str, str]] = {}
695        for cls in self._bases:
696            treeinfo = doc_ast.walk_tree(cls)
697            for name in (
698                treeinfo.var_docstrings.keys()
699                | treeinfo.func_docstrings.keys()
700                | treeinfo.annotations.keys()
701            ):
702                decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}"))
703            for name in cls.__dict__:
704                decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}"))
705        if decls.get("__init__", None) == ("builtins", "object.__init__"):
706            decls["__init__"] = (
707                self.obj.__module__,
708                f"{self.obj.__qualname__}.__init__",
709            )
710        return decls
711
712    def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]:
713        try:
714            return self._declarations[member_name]
715        except KeyError:  # pragma: no cover
716            # TypedDict botches __mro__ on Python <3.12 and may need special casing here.
717            # One workaround is to also specify TypedDict as a base class, see pdoc.doc.Class._bases.
718            warnings.warn(
719                f"Cannot determine where {self.fullname}.{member_name} is taken from, assuming current file."
720            )
721            return self.modulename, f"{self.qualname}.{member_name}"
722
723    @cached_property
724    def own_members(self) -> list[Doc]:
725        members = self._members_by_origin.get((self.modulename, self.qualname), [])
726        if self.taken_from != (self.modulename, self.qualname):
727            # .taken_from may be != (self.modulename, self.qualname), for example when
728            # a module re-exports a class from a private submodule.
729            members += self._members_by_origin.get(self.taken_from, [])
730        return members
731
732    @cached_property
733    def _member_objects(self) -> dict[str, Any]:
734        unsorted: dict[str, Any] = {}
735        for cls in self._bases:
736            for name, obj in cls.__dict__.items():
737                unsorted.setdefault(name, obj)
738        for name in self._var_docstrings:
739            unsorted.setdefault(name, empty)
740        for name in self._var_annotations:
741            unsorted.setdefault(name, empty)
742
743        init_has_no_doc = unsorted.get("__init__", object.__init__).__doc__ in (
744            None,
745            object.__init__.__doc__,
746        )
747        if init_has_no_doc:
748            if inspect.isabstract(self.obj):
749                # Special case: We don't want to show constructors for abstract base classes unless
750                # they have a custom docstring.
751                del unsorted["__init__"]
752            elif issubclass(self.obj, enum.Enum):
753                # Special case: Do not show a constructor for enums. They are typically not constructed by users.
754                # The alternative would be showing __new__, as __call__ is too verbose.
755                del unsorted["__init__"]
756            elif issubclass(self.obj, dict):
757                # Special case: Do not show a constructor for dict subclasses.
758                unsorted.pop(
759                    "__init__", None
760                )  # TypedDict subclasses may not have __init__.
761            else:
762                # Check if there's a helpful Metaclass.__call__ or Class.__new__. This dance is very similar to
763                # https://github.com/python/cpython/blob/9feae41c4f04ca27fd2c865807a5caeb50bf4fc4/Lib/inspect.py#L2359-L2376
764                call = _safe_getattr(type(self.obj), "__call__", None)
765                custom_call_with_custom_docstring = (
766                    call is not None
767                    and not isinstance(call, NonUserDefinedCallables)
768                    and call.__doc__ not in (None, object.__call__.__doc__)
769                )
770                if custom_call_with_custom_docstring:
771                    unsorted["__init__"] = call
772                else:
773                    # Does our class define a custom __new__ method?
774                    new = _safe_getattr(self.obj, "__new__", None)
775                    custom_new_with_custom_docstring = (
776                        new is not None
777                        and not isinstance(new, NonUserDefinedCallables)
778                        and new.__doc__ not in (None, object.__new__.__doc__)
779                    )
780                    if custom_new_with_custom_docstring:
781                        unsorted["__init__"] = new
782
783        sorted: dict[str, Any] = {}
784        for cls in self._bases:
785            sorted, unsorted = doc_ast.sort_by_source(cls, sorted, unsorted)
786        sorted.update(unsorted)
787
788        if _pydantic.is_pydantic_model(self.obj):
789            sorted = {
790                k: v for k, v in sorted.items() if k not in _pydantic.IGNORED_FIELDS
791            }
792
793        return sorted
794
795    @cached_property
796    def bases(self) -> list[tuple[str, str, str]]:
797        """
798        A list of all base classes, i.e. all immediate parent classes.
799
800        Each parent class is represented as a `(modulename, qualname, display_text)` tuple.
801        """
802        bases = []
803        for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__):
804            if x is object:
805                continue
806            o = get_origin(x)
807            if o:
808                bases.append((o.__module__, o.__qualname__, str(x)))
809            elif x.__module__ == self.modulename:
810                bases.append((x.__module__, x.__qualname__, x.__qualname__))
811            else:
812                bases.append(
813                    (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}")
814                )
815        return bases
816
817    @cached_property
818    def decorators(self) -> list[str]:
819        """A list of all decorators the class is decorated with."""
820        decorators = []
821        for t in doc_ast.parse(self.obj).decorator_list:
822            decorators.append(f"@{doc_ast.unparse(t)}")
823        return decorators
824
825    @cached_property
826    def class_variables(self) -> list[Variable]:
827        """
828        A list of all documented class variables in the class.
829
830        Class variables are variables that are explicitly annotated with `typing.ClassVar`.
831        All other variables are treated as instance variables.
832        """
833        return [
834            x
835            for x in self.members.values()
836            if isinstance(x, Variable) and x.is_classvar
837        ]
838
839    @cached_property
840    def instance_variables(self) -> list[Variable]:
841        """
842        A list of all instance variables in the class.
843        """
844        return [
845            x
846            for x in self.members.values()
847            if isinstance(x, Variable) and not x.is_classvar
848        ]
849
850    @cached_property
851    def classmethods(self) -> list[Function]:
852        """
853        A list of all documented `@classmethod`s.
854        """
855        return [
856            x
857            for x in self.members.values()
858            if isinstance(x, Function) and x.is_classmethod
859        ]
860
861    @cached_property
862    def staticmethods(self) -> list[Function]:
863        """
864        A list of all documented `@staticmethod`s.
865        """
866        return [
867            x
868            for x in self.members.values()
869            if isinstance(x, Function) and x.is_staticmethod
870        ]
871
872    @cached_property
873    def methods(self) -> list[Function]:
874        """
875        A list of all documented methods in the class that are neither static- nor classmethods.
876        """
877        return [
878            x
879            for x in self.members.values()
880            if isinstance(x, Function)
881            and not x.is_staticmethod
882            and not x.is_classmethod
883        ]

Representation of a class's documentation.

kind = 'class'

The type of the doc object, either "module", "class", "function", or "variable".

docstring: str
595    @cached_property
596    def docstring(self) -> str:
597        doc = Doc.docstring.__get__(self)  # type: ignore
598        if doc == dict.__doc__:
599            # Don't display default docstring for dict subclasses (primarily TypedDict).
600            return ""
601        if doc in _Enum_default_docstrings:
602            # Don't display default docstring for enum subclasses.
603            return ""
604        if dataclasses.is_dataclass(self.obj) and doc.startswith(self.obj.__name__):
605            try:
606                sig = inspect.signature(self.obj)
607            except Exception:
608                pass
609            else:
610                # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py
611                is_dataclass_with_default_docstring = doc == self.obj.__name__ + str(
612                    sig
613                ).replace(" -> None", "")
614                if is_dataclass_with_default_docstring:
615                    return ""
616        return doc

The docstring for this object. It has already been cleaned by inspect.cleandoc.

If no docstring can be found, an empty string is returned.

own_members: list[Doc]
723    @cached_property
724    def own_members(self) -> list[Doc]:
725        members = self._members_by_origin.get((self.modulename, self.qualname), [])
726        if self.taken_from != (self.modulename, self.qualname):
727            # .taken_from may be != (self.modulename, self.qualname), for example when
728            # a module re-exports a class from a private submodule.
729            members += self._members_by_origin.get(self.taken_from, [])
730        return members

A list of all own (i.e. non-inherited) members.

bases: list[tuple[str, str, str]]
795    @cached_property
796    def bases(self) -> list[tuple[str, str, str]]:
797        """
798        A list of all base classes, i.e. all immediate parent classes.
799
800        Each parent class is represented as a `(modulename, qualname, display_text)` tuple.
801        """
802        bases = []
803        for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__):
804            if x is object:
805                continue
806            o = get_origin(x)
807            if o:
808                bases.append((o.__module__, o.__qualname__, str(x)))
809            elif x.__module__ == self.modulename:
810                bases.append((x.__module__, x.__qualname__, x.__qualname__))
811            else:
812                bases.append(
813                    (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}")
814                )
815        return bases

A list of all base classes, i.e. all immediate parent classes.

Each parent class is represented as a (modulename, qualname, display_text) tuple.

decorators: list[str]
817    @cached_property
818    def decorators(self) -> list[str]:
819        """A list of all decorators the class is decorated with."""
820        decorators = []
821        for t in doc_ast.parse(self.obj).decorator_list:
822            decorators.append(f"@{doc_ast.unparse(t)}")
823        return decorators

A list of all decorators the class is decorated with.

class_variables: list[Variable]
825    @cached_property
826    def class_variables(self) -> list[Variable]:
827        """
828        A list of all documented class variables in the class.
829
830        Class variables are variables that are explicitly annotated with `typing.ClassVar`.
831        All other variables are treated as instance variables.
832        """
833        return [
834            x
835            for x in self.members.values()
836            if isinstance(x, Variable) and x.is_classvar
837        ]

A list of all documented class variables in the class.

Class variables are variables that are explicitly annotated with typing.ClassVar. All other variables are treated as instance variables.

instance_variables: list[Variable]
839    @cached_property
840    def instance_variables(self) -> list[Variable]:
841        """
842        A list of all instance variables in the class.
843        """
844        return [
845            x
846            for x in self.members.values()
847            if isinstance(x, Variable) and not x.is_classvar
848        ]

A list of all instance variables in the class.

classmethods: list[Function]
850    @cached_property
851    def classmethods(self) -> list[Function]:
852        """
853        A list of all documented `@classmethod`s.
854        """
855        return [
856            x
857            for x in self.members.values()
858            if isinstance(x, Function) and x.is_classmethod
859        ]

A list of all documented @classmethods.

staticmethods: list[Function]
861    @cached_property
862    def staticmethods(self) -> list[Function]:
863        """
864        A list of all documented `@staticmethod`s.
865        """
866        return [
867            x
868            for x in self.members.values()
869            if isinstance(x, Function) and x.is_staticmethod
870        ]

A list of all documented @staticmethods.

methods: list[Function]
872    @cached_property
873    def methods(self) -> list[Function]:
874        """
875        A list of all documented methods in the class that are neither static- nor classmethods.
876        """
877        return [
878            x
879            for x in self.members.values()
880            if isinstance(x, Function)
881            and not x.is_staticmethod
882            and not x.is_classmethod
883        ]

A list of all documented methods in the class that are neither static- nor classmethods.

WrappedFunction = function | staticmethod | classmethod
class Function(pdoc.doc.Doc[function]):
 889class Function(Doc[types.FunctionType]):
 890    """
 891    Representation of a function's documentation.
 892
 893    This class covers all "flavors" of functions, for example it also
 894    supports `@classmethod`s or `@staticmethod`s.
 895    """
 896
 897    kind = "function"
 898
 899    wrapped: WrappedFunction
 900    """The original wrapped function (e.g., `staticmethod(func)`)"""
 901
 902    obj: types.FunctionType
 903    """The unwrapped "real" function."""
 904
 905    def __init__(
 906        self,
 907        modulename: str,
 908        qualname: str,
 909        func: WrappedFunction,
 910        taken_from: tuple[str, str],
 911    ):
 912        """Initialize a function's documentation object."""
 913        unwrapped: types.FunctionType
 914        if isinstance(func, (classmethod, staticmethod)):
 915            unwrapped = func.__func__  # type: ignore
 916        elif isinstance(func, singledispatchmethod):
 917            unwrapped = func.func  # type: ignore
 918        elif hasattr(func, "__wrapped__"):
 919            unwrapped = func.__wrapped__
 920        else:
 921            unwrapped = func
 922        super().__init__(modulename, qualname, unwrapped, taken_from)
 923        self.wrapped = func  # type: ignore
 924
 925    @cache
 926    @_include_fullname_in_traceback
 927    def __repr__(self):
 928        if self.is_classmethod:
 929            t = "class"
 930        elif self.is_staticmethod:
 931            t = "static"
 932        elif self.qualname != _safe_getattr(self.obj, "__name__", None):
 933            t = "method"
 934        else:
 935            t = "function"
 936        return f"<{_decorators(self)}{t} {self.funcdef} {self.name}{self.signature}: ...{_docstr(self)}>"
 937
 938    @cached_property
 939    def docstring(self) -> str:
 940        doc = Doc.docstring.__get__(self)  # type: ignore
 941        if not doc:
 942            # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors.
 943            # We now do an ugly dance to obtain the bound object instead,
 944            # that somewhat resembles what inspect._findclass is doing.
 945            cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None)
 946            for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]:
 947                cls = _safe_getattr(cls, name, None)
 948
 949            unbound = _safe_getattr(cls, "__dict__", {}).get(self.name)
 950            is_classmethod_property = isinstance(unbound, classmethod) and isinstance(
 951                unbound.__func__, (property, cached_property)
 952            )
 953            if not is_classmethod_property:
 954                # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11.
 955                # Directly accessing them would give us the return value, which has the wrong docstring.
 956                doc = _safe_getdoc(_safe_getattr(cls, self.name, None))
 957
 958        if doc == object.__init__.__doc__:
 959            # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined...
 960            return ""
 961        else:
 962            return doc
 963
 964    @cached_property
 965    def is_classmethod(self) -> bool:
 966        """
 967        `True` if this function is a `@classmethod`, `False` otherwise.
 968        """
 969        return isinstance(self.wrapped, classmethod)
 970
 971    @cached_property
 972    def is_staticmethod(self) -> bool:
 973        """
 974        `True` if this function is a `@staticmethod`, `False` otherwise.
 975        """
 976        return isinstance(self.wrapped, staticmethod)
 977
 978    @cached_property
 979    def decorators(self) -> list[str]:
 980        """A list of all decorators the function is decorated with."""
 981        decorators = []
 982        obj: types.FunctionType = self.obj  # type: ignore
 983        for t in doc_ast.parse(obj).decorator_list:
 984            decorators.append(f"@{doc_ast.unparse(t)}")
 985        return decorators
 986
 987    @cached_property
 988    def funcdef(self) -> str:
 989        """
 990        The string of keywords used to define the function, i.e. `"def"` or `"async def"`.
 991        """
 992        if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction(
 993            self.obj
 994        ):
 995            return "async def"
 996        else:
 997            return "def"
 998
 999    @cached_property
1000    def signature(self) -> inspect.Signature:
1001        """
1002        The function's signature.
1003
1004        This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature`
1005        that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines
1006        in repr(). Additionally, all types are already resolved.
1007
1008        If the signature cannot be determined, a placeholder Signature object is returned.
1009        """
1010        if self.obj is object.__init__:
1011            # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs)
1012            # signature for the default __init__ method.
1013            return inspect.Signature()
1014        try:
1015            sig = _PrettySignature.from_callable(self.obj)
1016        except Exception:
1017            return inspect.Signature(
1018                [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)]
1019            )
1020        mod = inspect.getmodule(self.obj)
1021        globalns = _safe_getattr(mod, "__dict__", {})
1022        localns = globalns
1023        for parent_cls_name in self.qualname.split(".")[:-1]:
1024            parent_cls = localns.get(parent_cls_name, object)
1025            localns = _safe_getattr(parent_cls, "__dict__", None)
1026            if localns is None:
1027                break  # pragma: no cover
1028
1029        if self.name == "__init__":
1030            sig = sig.replace(return_annotation=empty)
1031        else:
1032            sig = sig.replace(
1033                return_annotation=safe_eval_type(
1034                    sig.return_annotation, globalns, localns, mod, self.fullname
1035                )
1036            )
1037        for p in sig.parameters.values():
1038            p._annotation = safe_eval_type(  # type: ignore
1039                p.annotation, globalns, localns, mod, self.fullname
1040            )
1041        return sig
1042
1043    @cached_property
1044    def signature_without_self(self) -> inspect.Signature:
1045        """Like `signature`, but without the first argument.
1046
1047        This is useful to display constructors.
1048        """
1049        return self.signature.replace(
1050            parameters=list(self.signature.parameters.values())[1:]
1051        )

Representation of a function's documentation.

This class covers all "flavors" of functions, for example it also supports @classmethods or @staticmethods.

Function( modulename: str, qualname: str, func: function | staticmethod | classmethod, taken_from: tuple[str, str])
905    def __init__(
906        self,
907        modulename: str,
908        qualname: str,
909        func: WrappedFunction,
910        taken_from: tuple[str, str],
911    ):
912        """Initialize a function's documentation object."""
913        unwrapped: types.FunctionType
914        if isinstance(func, (classmethod, staticmethod)):
915            unwrapped = func.__func__  # type: ignore
916        elif isinstance(func, singledispatchmethod):
917            unwrapped = func.func  # type: ignore
918        elif hasattr(func, "__wrapped__"):
919            unwrapped = func.__wrapped__
920        else:
921            unwrapped = func
922        super().__init__(modulename, qualname, unwrapped, taken_from)
923        self.wrapped = func  # type: ignore

Initialize a function's documentation object.

kind = 'function'

The type of the doc object, either "module", "class", "function", or "variable".

wrapped: function | staticmethod | classmethod

The original wrapped function (e.g., staticmethod(func))

obj: function

The unwrapped "real" function.

docstring: str
938    @cached_property
939    def docstring(self) -> str:
940        doc = Doc.docstring.__get__(self)  # type: ignore
941        if not doc:
942            # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors.
943            # We now do an ugly dance to obtain the bound object instead,
944            # that somewhat resembles what inspect._findclass is doing.
945            cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None)
946            for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]:
947                cls = _safe_getattr(cls, name, None)
948
949            unbound = _safe_getattr(cls, "__dict__", {}).get(self.name)
950            is_classmethod_property = isinstance(unbound, classmethod) and isinstance(
951                unbound.__func__, (property, cached_property)
952            )
953            if not is_classmethod_property:
954                # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11.
955                # Directly accessing them would give us the return value, which has the wrong docstring.
956                doc = _safe_getdoc(_safe_getattr(cls, self.name, None))
957
958        if doc == object.__init__.__doc__:
959            # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined...
960            return ""
961        else:
962            return doc

The docstring for this object. It has already been cleaned by inspect.cleandoc.

If no docstring can be found, an empty string is returned.

is_classmethod: bool
964    @cached_property
965    def is_classmethod(self) -> bool:
966        """
967        `True` if this function is a `@classmethod`, `False` otherwise.
968        """
969        return isinstance(self.wrapped, classmethod)

True if this function is a @classmethod, False otherwise.

is_staticmethod: bool
971    @cached_property
972    def is_staticmethod(self) -> bool:
973        """
974        `True` if this function is a `@staticmethod`, `False` otherwise.
975        """
976        return isinstance(self.wrapped, staticmethod)

True if this function is a @staticmethod, False otherwise.

decorators: list[str]
978    @cached_property
979    def decorators(self) -> list[str]:
980        """A list of all decorators the function is decorated with."""
981        decorators = []
982        obj: types.FunctionType = self.obj  # type: ignore
983        for t in doc_ast.parse(obj).decorator_list:
984            decorators.append(f"@{doc_ast.unparse(t)}")
985        return decorators

A list of all decorators the function is decorated with.

funcdef: str
987    @cached_property
988    def funcdef(self) -> str:
989        """
990        The string of keywords used to define the function, i.e. `"def"` or `"async def"`.
991        """
992        if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction(
993            self.obj
994        ):
995            return "async def"
996        else:
997            return "def"

The string of keywords used to define the function, i.e. "def" or "async def".

signature: inspect.Signature
 999    @cached_property
1000    def signature(self) -> inspect.Signature:
1001        """
1002        The function's signature.
1003
1004        This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature`
1005        that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines
1006        in repr(). Additionally, all types are already resolved.
1007
1008        If the signature cannot be determined, a placeholder Signature object is returned.
1009        """
1010        if self.obj is object.__init__:
1011            # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs)
1012            # signature for the default __init__ method.
1013            return inspect.Signature()
1014        try:
1015            sig = _PrettySignature.from_callable(self.obj)
1016        except Exception:
1017            return inspect.Signature(
1018                [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)]
1019            )
1020        mod = inspect.getmodule(self.obj)
1021        globalns = _safe_getattr(mod, "__dict__", {})
1022        localns = globalns
1023        for parent_cls_name in self.qualname.split(".")[:-1]:
1024            parent_cls = localns.get(parent_cls_name, object)
1025            localns = _safe_getattr(parent_cls, "__dict__", None)
1026            if localns is None:
1027                break  # pragma: no cover
1028
1029        if self.name == "__init__":
1030            sig = sig.replace(return_annotation=empty)
1031        else:
1032            sig = sig.replace(
1033                return_annotation=safe_eval_type(
1034                    sig.return_annotation, globalns, localns, mod, self.fullname
1035                )
1036            )
1037        for p in sig.parameters.values():
1038            p._annotation = safe_eval_type(  # type: ignore
1039                p.annotation, globalns, localns, mod, self.fullname
1040            )
1041        return sig

The function's signature.

This usually returns an instance of _PrettySignature, a subclass of inspect.Signature that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines in repr(). Additionally, all types are already resolved.

If the signature cannot be determined, a placeholder Signature object is returned.

signature_without_self: inspect.Signature
1043    @cached_property
1044    def signature_without_self(self) -> inspect.Signature:
1045        """Like `signature`, but without the first argument.
1046
1047        This is useful to display constructors.
1048        """
1049        return self.signature.replace(
1050            parameters=list(self.signature.parameters.values())[1:]
1051        )

Like signature, but without the first argument.

This is useful to display constructors.

class Variable(pdoc.doc.Doc[NoneType]):
1054class Variable(Doc[None]):
1055    """
1056    Representation of a variable's documentation. This includes module, class and instance variables.
1057    """
1058
1059    kind = "variable"
1060
1061    default_value: (
1062        Any | empty
1063    )  # technically Any includes empty, but this conveys intent.
1064    """
1065    The variable's default value.
1066    
1067    In some cases, no default value is known. This may either be because a variable is only defined in the constructor,
1068    or it is only declared with a type annotation without assignment (`foo: int`).
1069    To distinguish this case from a default value of `None`, `pdoc.doc_types.empty` is used as a placeholder.
1070    """
1071
1072    annotation: type | empty
1073    """
1074    The variable's type annotation.
1075    
1076    If there is no type annotation, `pdoc.doc_types.empty` is used as a placeholder.
1077    """
1078
1079    def __init__(
1080        self,
1081        modulename: str,
1082        qualname: str,
1083        *,
1084        taken_from: tuple[str, str],
1085        docstring: str,
1086        annotation: type | empty = empty,
1087        default_value: Any | empty = empty,
1088    ):
1089        """
1090        Construct a variable doc object.
1091
1092        While classes and functions can introspect themselves to see their docstring,
1093        variables can't do that as we don't have a "variable object" we could query.
1094        As such, docstring, declaration location, type annotation, and the default value
1095        must be passed manually in the constructor.
1096        """
1097        super().__init__(modulename, qualname, None, taken_from)
1098        # noinspection PyPropertyAccess
1099        self.docstring = inspect.cleandoc(docstring)
1100        self.annotation = annotation
1101        self.default_value = default_value
1102
1103    @cache
1104    @_include_fullname_in_traceback
1105    def __repr__(self):
1106        if self.default_value_str:
1107            default = f" = {self.default_value_str}"
1108        else:
1109            default = ""
1110        return f"<var {self.qualname.rsplit('.')[-1]}{self.annotation_str}{default}{_docstr(self)}>"
1111
1112    @cached_property
1113    def is_classvar(self) -> bool:
1114        """`True` if the variable is a class variable, `False` otherwise."""
1115        if get_origin(self.annotation) is ClassVar:
1116            return True
1117        else:
1118            return False
1119
1120    @cached_property
1121    def is_typevar(self) -> bool:
1122        """`True` if the variable is a `typing.TypeVar`, `False` otherwise."""
1123        if isinstance(self.default_value, TypeVar):
1124            return True
1125        else:
1126            return False
1127
1128    @cached_property
1129    def is_type_alias_type(self) -> bool:
1130        """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise."""
1131        return isinstance(self.default_value, TypeAliasType)
1132
1133    @cached_property
1134    def is_enum_member(self) -> bool:
1135        """`True` if the variable is an enum member, `False` otherwise."""
1136        if isinstance(self.default_value, enum.Enum):
1137            return True
1138        else:
1139            return False
1140
1141    @cached_property
1142    def default_value_str(self) -> str:
1143        """The variable's default value as a pretty-printed str."""
1144        if self.default_value is empty:
1145            return ""
1146        if isinstance(self.default_value, TypeAliasType):
1147            formatted = formatannotation(self.default_value.__value__)
1148            return _remove_collections_abc(formatted)
1149        elif self.annotation == TypeAlias:
1150            formatted = formatannotation(self.default_value)
1151            return _remove_collections_abc(formatted)
1152
1153        # This is not perfect, but a solid attempt at preventing accidental leakage of secrets.
1154        # If you have input on how to improve the heuristic, please send a pull request!
1155        value_taken_from_env_var = (
1156            isinstance(self.default_value, str)
1157            and len(self.default_value) >= 8
1158            and self.default_value in _environ_lookup()
1159        )
1160        if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""):
1161            env_var = "$" + _environ_lookup()[self.default_value]
1162            warnings.warn(
1163                f"The default value of {self.fullname} matches the {env_var} environment variable. "
1164                f"To prevent accidental leakage of secrets, the default value is not displayed. "
1165                f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.",
1166                RuntimeWarning,
1167            )
1168            return env_var
1169
1170        try:
1171            pretty = repr(self.default_value)
1172        except Exception as e:
1173            warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})")
1174            return ""
1175
1176        pretty = _remove_memory_addresses(pretty)
1177        return pretty
1178
1179    @cached_property
1180    def annotation_str(self) -> str:
1181        """The variable's type annotation as a pretty-printed str."""
1182        if self.annotation is not empty:
1183            formatted = formatannotation(self.annotation)
1184            # type aliases don't include the module name in their __repr__, so we add it here.
1185            if isinstance(self.annotation, TypeAliasType):
1186                formatted = f"{self.annotation.__module__}.{formatted}"
1187            return f": {_remove_collections_abc(formatted)}"
1188        else:
1189            return ""

Representation of a variable's documentation. This includes module, class and instance variables.

Variable( modulename: str, qualname: str, *, taken_from: tuple[str, str], docstring: str, annotation: type | inspect._empty, default_value: Any | inspect._empty)
1079    def __init__(
1080        self,
1081        modulename: str,
1082        qualname: str,
1083        *,
1084        taken_from: tuple[str, str],
1085        docstring: str,
1086        annotation: type | empty = empty,
1087        default_value: Any | empty = empty,
1088    ):
1089        """
1090        Construct a variable doc object.
1091
1092        While classes and functions can introspect themselves to see their docstring,
1093        variables can't do that as we don't have a "variable object" we could query.
1094        As such, docstring, declaration location, type annotation, and the default value
1095        must be passed manually in the constructor.
1096        """
1097        super().__init__(modulename, qualname, None, taken_from)
1098        # noinspection PyPropertyAccess
1099        self.docstring = inspect.cleandoc(docstring)
1100        self.annotation = annotation
1101        self.default_value = default_value

Construct a variable doc object.

While classes and functions can introspect themselves to see their docstring, variables can't do that as we don't have a "variable object" we could query. As such, docstring, declaration location, type annotation, and the default value must be passed manually in the constructor.

kind = 'variable'

The type of the doc object, either "module", "class", "function", or "variable".

default_value: Any | inspect._empty

The variable's default value.

In some cases, no default value is known. This may either be because a variable is only defined in the constructor, or it is only declared with a type annotation without assignment (foo: int). To distinguish this case from a default value of None, pdoc.doc_types.empty is used as a placeholder.

annotation: type | inspect._empty

The variable's type annotation.

If there is no type annotation, pdoc.doc_types.empty is used as a placeholder.

docstring: str
158    @cached_property
159    def docstring(self) -> str:
160        """
161        The docstring for this object. It has already been cleaned by `inspect.cleandoc`.
162
163        If no docstring can be found, an empty string is returned.
164        """
165        return _safe_getdoc(self.obj)

The docstring for this object. It has already been cleaned by inspect.cleandoc.

If no docstring can be found, an empty string is returned.

is_classvar: bool
1112    @cached_property
1113    def is_classvar(self) -> bool:
1114        """`True` if the variable is a class variable, `False` otherwise."""
1115        if get_origin(self.annotation) is ClassVar:
1116            return True
1117        else:
1118            return False

True if the variable is a class variable, False otherwise.

is_typevar: bool
1120    @cached_property
1121    def is_typevar(self) -> bool:
1122        """`True` if the variable is a `typing.TypeVar`, `False` otherwise."""
1123        if isinstance(self.default_value, TypeVar):
1124            return True
1125        else:
1126            return False

True if the variable is a typing.TypeVar, False otherwise.

is_type_alias_type: bool
1128    @cached_property
1129    def is_type_alias_type(self) -> bool:
1130        """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise."""
1131        return isinstance(self.default_value, TypeAliasType)

True if the variable is a typing.TypeAliasType, False otherwise.

is_enum_member: bool
1133    @cached_property
1134    def is_enum_member(self) -> bool:
1135        """`True` if the variable is an enum member, `False` otherwise."""
1136        if isinstance(self.default_value, enum.Enum):
1137            return True
1138        else:
1139            return False

True if the variable is an enum member, False otherwise.

default_value_str: str
1141    @cached_property
1142    def default_value_str(self) -> str:
1143        """The variable's default value as a pretty-printed str."""
1144        if self.default_value is empty:
1145            return ""
1146        if isinstance(self.default_value, TypeAliasType):
1147            formatted = formatannotation(self.default_value.__value__)
1148            return _remove_collections_abc(formatted)
1149        elif self.annotation == TypeAlias:
1150            formatted = formatannotation(self.default_value)
1151            return _remove_collections_abc(formatted)
1152
1153        # This is not perfect, but a solid attempt at preventing accidental leakage of secrets.
1154        # If you have input on how to improve the heuristic, please send a pull request!
1155        value_taken_from_env_var = (
1156            isinstance(self.default_value, str)
1157            and len(self.default_value) >= 8
1158            and self.default_value in _environ_lookup()
1159        )
1160        if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""):
1161            env_var = "$" + _environ_lookup()[self.default_value]
1162            warnings.warn(
1163                f"The default value of {self.fullname} matches the {env_var} environment variable. "
1164                f"To prevent accidental leakage of secrets, the default value is not displayed. "
1165                f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.",
1166                RuntimeWarning,
1167            )
1168            return env_var
1169
1170        try:
1171            pretty = repr(self.default_value)
1172        except Exception as e:
1173            warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})")
1174            return ""
1175
1176        pretty = _remove_memory_addresses(pretty)
1177        return pretty

The variable's default value as a pretty-printed str.

annotation_str: str
1179    @cached_property
1180    def annotation_str(self) -> str:
1181        """The variable's type annotation as a pretty-printed str."""
1182        if self.annotation is not empty:
1183            formatted = formatannotation(self.annotation)
1184            # type aliases don't include the module name in their __repr__, so we add it here.
1185            if isinstance(self.annotation, TypeAliasType):
1186                formatted = f"{self.annotation.__module__}.{formatted}"
1187            return f": {_remove_collections_abc(formatted)}"
1188        else:
1189            return ""

The variable's type annotation as a pretty-printed str.