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