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

A base class for all documentation objects.

Doc(modulename: str, qualname: str, obj: ~T, taken_from: tuple[str, str])
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

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
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
fullname: str
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(".")

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

name: str
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]

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

docstring: str
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)

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
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)

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
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

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

source_lines: tuple[int, int] | None
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

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

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

is_inherited: bool
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

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

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

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

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

flattened_own_members: list[Doc]
359    @cached_property
360    def flattened_own_members(self) -> list[Doc]:
361        """
362        A list of all documented members and their child classes, recursively.
363        """
364        flattened = []
365        for x in self.own_members:
366            flattened.append(x)
367            if isinstance(x, Class):
368                flattened.extend(
369                    [cls for cls in x.flattened_own_members if isinstance(cls, Class)]
370                )
371        return flattened

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

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

Representation of a module's documentation.

Module(module: module)
391    def __init__(
392        self,
393        module: types.ModuleType,
394    ):
395        """
396        Creates a documentation object given the actual
397        Python module object.
398        """
399        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:
403    @classmethod
404    @cache
405    def from_name(cls, name: str) -> Module:
406        """Create a `Module` object by supplying the module's (full) name."""
407        return cls(extract.load_module(name))

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

is_package: bool
414    @cached_property
415    def is_package(self) -> bool:
416        """
417        `True` if the module is a package, `False` otherwise.
418
419        Packages are a special kind of module that may have submodules.
420        Typically, this means that this file is in a directory named like the
421        module with the name `__init__.py`.
422        """
423        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]
457    @cached_property
458    def own_members(self) -> list[Doc]:
459        return list(self.members.values())

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

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

A list of all (direct) submodules.

variables: list[Variable]
553    @cached_property
554    def variables(self) -> list[Variable]:
555        """
556        A list of all documented module level variables.
557        """
558        return [x for x in self.members.values() if isinstance(x, Variable)]

A list of all documented module level variables.

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

A list of all documented module level classes.

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

Representation of a class's documentation.

kind = 'class'

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

docstring: str
587    @cached_property
588    def docstring(self) -> str:
589        doc = Doc.docstring.__get__(self)  # type: ignore
590        if doc == dict.__doc__:
591            # Don't display default docstring for dict subclasses (primarily TypedDict).
592            return ""
593        if doc in _Enum_default_docstrings:
594            # Don't display default docstring for enum subclasses.
595            return ""
596        if dataclasses.is_dataclass(self.obj) and doc.startswith(self.obj.__name__):
597            try:
598                sig = inspect.signature(self.obj)
599            except Exception:
600                pass
601            else:
602                # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py
603                is_dataclass_with_default_docstring = doc == self.obj.__name__ + str(
604                    sig
605                ).replace(" -> None", "")
606                if is_dataclass_with_default_docstring:
607                    return ""
608        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]
715    @cached_property
716    def own_members(self) -> list[Doc]:
717        members = self._members_by_origin.get((self.modulename, self.qualname), [])
718        if self.taken_from != (self.modulename, self.qualname):
719            # .taken_from may be != (self.modulename, self.qualname), for example when
720            # a module re-exports a class from a private submodule.
721            members += self._members_by_origin.get(self.taken_from, [])
722        return members

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

bases: list[tuple[str, str, str]]
781    @cached_property
782    def bases(self) -> list[tuple[str, str, str]]:
783        """
784        A list of all base classes, i.e. all immediate parent classes.
785
786        Each parent class is represented as a `(modulename, qualname, display_text)` tuple.
787        """
788        bases = []
789        for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__):
790            if x is object:
791                continue
792            o = get_origin(x)
793            if o:
794                bases.append((o.__module__, o.__qualname__, str(x)))
795            elif x.__module__ == self.modulename:
796                bases.append((x.__module__, x.__qualname__, x.__qualname__))
797            else:
798                bases.append(
799                    (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}")
800                )
801        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]
803    @cached_property
804    def decorators(self) -> list[str]:
805        """A list of all decorators the class is decorated with."""
806        decorators = []
807        for t in doc_ast.parse(self.obj).decorator_list:
808            decorators.append(f"@{doc_ast.unparse(t)}")
809        return decorators

A list of all decorators the class is decorated with.

class_variables: list[Variable]
811    @cached_property
812    def class_variables(self) -> list[Variable]:
813        """
814        A list of all documented class variables in the class.
815
816        Class variables are variables that are explicitly annotated with `typing.ClassVar`.
817        All other variables are treated as instance variables.
818        """
819        return [
820            x
821            for x in self.members.values()
822            if isinstance(x, Variable) and x.is_classvar
823        ]

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]
825    @cached_property
826    def instance_variables(self) -> list[Variable]:
827        """
828        A list of all instance variables in the class.
829        """
830        return [
831            x
832            for x in self.members.values()
833            if isinstance(x, Variable) and not x.is_classvar
834        ]

A list of all instance variables in the class.

classmethods: list[Function]
836    @cached_property
837    def classmethods(self) -> list[Function]:
838        """
839        A list of all documented `@classmethod`s.
840        """
841        return [
842            x
843            for x in self.members.values()
844            if isinstance(x, Function) and x.is_classmethod
845        ]

A list of all documented @classmethods.

staticmethods: list[Function]
847    @cached_property
848    def staticmethods(self) -> list[Function]:
849        """
850        A list of all documented `@staticmethod`s.
851        """
852        return [
853            x
854            for x in self.members.values()
855            if isinstance(x, Function) and x.is_staticmethod
856        ]

A list of all documented @staticmethods.

methods: list[Function]
858    @cached_property
859    def methods(self) -> list[Function]:
860        """
861        A list of all documented methods in the class that are neither static- nor classmethods.
862        """
863        return [
864            x
865            for x in self.members.values()
866            if isinstance(x, Function)
867            and not x.is_staticmethod
868            and not x.is_classmethod
869        ]

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

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

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])
894    def __init__(
895        self,
896        modulename: str,
897        qualname: str,
898        func: WrappedFunction,
899        taken_from: tuple[str, str],
900    ):
901        """Initialize a function's documentation object."""
902        unwrapped: types.FunctionType
903        if isinstance(func, (classmethod, staticmethod)):
904            unwrapped = func.__func__  # type: ignore
905        elif isinstance(func, singledispatchmethod):
906            unwrapped = func.func  # type: ignore
907        elif hasattr(func, "__wrapped__"):
908            unwrapped = func.__wrapped__
909        else:
910            unwrapped = func
911        super().__init__(modulename, qualname, unwrapped, taken_from)
912        self.wrapped = func

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
927    @cached_property
928    def docstring(self) -> str:
929        doc = Doc.docstring.__get__(self)  # type: ignore
930        if not doc:
931            # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors.
932            # We now do an ugly dance to obtain the bound object instead,
933            # that somewhat resembles what inspect._findclass is doing.
934            cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None)
935            for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]:
936                cls = _safe_getattr(cls, name, None)
937
938            unbound = _safe_getattr(cls, "__dict__", {}).get(self.name)
939            is_classmethod_property = isinstance(unbound, classmethod) and isinstance(
940                unbound.__func__, (property, cached_property)
941            )
942            if not is_classmethod_property:
943                # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11.
944                # Directly accessing them would give us the return value, which has the wrong docstring.
945                doc = _safe_getdoc(_safe_getattr(cls, self.name, None))
946
947        if doc == object.__init__.__doc__:
948            # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined...
949            return ""
950        else:
951            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
953    @cached_property
954    def is_classmethod(self) -> bool:
955        """
956        `True` if this function is a `@classmethod`, `False` otherwise.
957        """
958        return isinstance(self.wrapped, classmethod)

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

is_staticmethod: bool
960    @cached_property
961    def is_staticmethod(self) -> bool:
962        """
963        `True` if this function is a `@staticmethod`, `False` otherwise.
964        """
965        return isinstance(self.wrapped, staticmethod)

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

decorators: list[str]
967    @cached_property
968    def decorators(self) -> list[str]:
969        """A list of all decorators the function is decorated with."""
970        decorators = []
971        obj: types.FunctionType = self.obj  # type: ignore
972        for t in doc_ast.parse(obj).decorator_list:
973            decorators.append(f"@{doc_ast.unparse(t)}")
974        return decorators

A list of all decorators the function is decorated with.

funcdef: str
976    @cached_property
977    def funcdef(self) -> str:
978        """
979        The string of keywords used to define the function, i.e. `"def"` or `"async def"`.
980        """
981        if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction(
982            self.obj
983        ):
984            return "async def"
985        else:
986            return "def"

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

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

Like signature, but without the first argument.

This is useful to display constructors.

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

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
1101    @cached_property
1102    def is_classvar(self) -> bool:
1103        """`True` if the variable is a class variable, `False` otherwise."""
1104        if get_origin(self.annotation) is ClassVar:
1105            return True
1106        else:
1107            return False

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

is_typevar: bool
1109    @cached_property
1110    def is_typevar(self) -> bool:
1111        """`True` if the variable is a `typing.TypeVar`, `False` otherwise."""
1112        if isinstance(self.default_value, TypeVar):
1113            return True
1114        else:
1115            return False

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

is_type_alias_type: bool
1117    @cached_property
1118    def is_type_alias_type(self) -> bool:
1119        """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise."""
1120        return isinstance(self.default_value, TypeAliasType)

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

is_enum_member: bool
1122    @cached_property
1123    def is_enum_member(self) -> bool:
1124        """`True` if the variable is an enum member, `False` otherwise."""
1125        if isinstance(self.default_value, enum.Enum):
1126            return True
1127        else:
1128            return False

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

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

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

annotation_str: str
1168    @cached_property
1169    def annotation_str(self) -> str:
1170        """The variable's type annotation as a pretty-printed str."""
1171        if self.annotation is not empty:
1172            formatted = formatannotation(self.annotation)
1173            return f": {_remove_collections_abc(formatted)}"
1174        else:
1175            return ""

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