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 is_dataclass_with_default_docstring = ( 591 dataclasses.is_dataclass(self.obj) 592 # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py 593 and doc 594 == self.obj.__name__ 595 + str(inspect.signature(self.obj)).replace(" -> None", "") 596 ) 597 if is_dataclass_with_default_docstring: 598 return "" 599 return doc 600 601 @cached_property 602 def _var_docstrings(self) -> dict[str, str]: 603 docstrings: dict[str, str] = {} 604 for cls in self._bases: 605 for name, docstr in doc_ast.walk_tree(cls).var_docstrings.items(): 606 docstrings.setdefault(name, docstr) 607 return docstrings 608 609 @cached_property 610 def _func_docstrings(self) -> dict[str, str]: 611 docstrings: dict[str, str] = {} 612 for cls in self._bases: 613 for name, docstr in doc_ast.walk_tree(cls).func_docstrings.items(): 614 docstrings.setdefault(name, docstr) 615 return docstrings 616 617 @cached_property 618 def _var_annotations(self) -> dict[str, type]: 619 # this is a bit tricky: __annotations__ also includes annotations from parent classes, 620 # but we need to execute them in the namespace of the parent class. 621 # Our workaround for this is to walk the MRO backwards, and only update/evaluate only if the annotation changes. 622 annotations: dict[ 623 str, tuple[Any, type] 624 ] = {} # attribute -> (annotation_unresolved, annotation_resolved) 625 for cls in reversed(self._bases): 626 cls_annotations = doc_ast.walk_tree(cls).annotations.copy() 627 dynamic_annotations = _safe_getattr(cls, "__annotations__", None) 628 if isinstance(dynamic_annotations, dict): 629 for attr, unresolved_annotation in dynamic_annotations.items(): 630 cls_annotations[attr] = unresolved_annotation 631 cls_fullname = ( 632 _safe_getattr(cls, "__module__", "") + "." + cls.__qualname__ 633 ).lstrip(".") 634 635 new_annotations = { 636 attr: unresolved_annotation 637 for attr, unresolved_annotation in cls_annotations.items() 638 if attr not in annotations 639 or annotations[attr][0] is not unresolved_annotation 640 } 641 localns = _safe_getattr(cls, "__dict__", None) 642 for attr, t in resolve_annotations( 643 new_annotations, inspect.getmodule(cls), localns, cls_fullname 644 ).items(): 645 annotations[attr] = (new_annotations[attr], t) 646 647 return {k: v[1] for k, v in annotations.items()} 648 649 @cached_property 650 def _bases(self) -> tuple[type, ...]: 651 orig_bases = _safe_getattr(self.obj, "__orig_bases__", ()) 652 old_python_typeddict_workaround = ( 653 sys.version_info < (3, 12) 654 and orig_bases 655 and _safe_getattr(orig_bases[-1], "__name__", None) == "TypedDict" 656 ) 657 if old_python_typeddict_workaround: # pragma: no cover 658 # TypedDicts on Python <3.12 have a botched __mro__. We need to fix it. 659 return (self.obj, *orig_bases[:-1]) 660 661 # __mro__ and __orig_bases__ differ between Python versions and special cases like TypedDict/NamedTuple. 662 # This here is a pragmatic approximation of what we want. 663 return ( 664 *(base for base in orig_bases if isinstance(base, type)), 665 *self.obj.__mro__, 666 ) 667 668 @cached_property 669 def _declarations(self) -> dict[str, tuple[str, str]]: 670 decls: dict[str, tuple[str, str]] = {} 671 for cls in self._bases: 672 treeinfo = doc_ast.walk_tree(cls) 673 for name in ( 674 treeinfo.var_docstrings.keys() 675 | treeinfo.func_docstrings.keys() 676 | treeinfo.annotations.keys() 677 ): 678 decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}")) 679 for name in cls.__dict__: 680 decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}")) 681 if decls.get("__init__", None) == ("builtins", "object.__init__"): 682 decls["__init__"] = ( 683 self.obj.__module__, 684 f"{self.obj.__qualname__}.__init__", 685 ) 686 return decls 687 688 def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]: 689 try: 690 return self._declarations[member_name] 691 except KeyError: # pragma: no cover 692 # TypedDict botches __mro__ on Python <3.12 and may need special casing here. 693 # One workaround is to also specify TypedDict as a base class, see pdoc.doc.Class._bases. 694 warnings.warn( 695 f"Cannot determine where {self.fullname}.{member_name} is taken from, assuming current file." 696 ) 697 return self.modulename, f"{self.qualname}.{member_name}" 698 699 @cached_property 700 def own_members(self) -> list[Doc]: 701 members = self._members_by_origin.get((self.modulename, self.qualname), []) 702 if self.taken_from != (self.modulename, self.qualname): 703 # .taken_from may be != (self.modulename, self.qualname), for example when 704 # a module re-exports a class from a private submodule. 705 members += self._members_by_origin.get(self.taken_from, []) 706 return members 707 708 @cached_property 709 def _member_objects(self) -> dict[str, Any]: 710 unsorted: dict[str, Any] = {} 711 for cls in self._bases: 712 for name, obj in cls.__dict__.items(): 713 unsorted.setdefault(name, obj) 714 for name in self._var_docstrings: 715 unsorted.setdefault(name, empty) 716 for name in self._var_annotations: 717 unsorted.setdefault(name, empty) 718 719 init_has_no_doc = unsorted.get("__init__", object.__init__).__doc__ in ( 720 None, 721 object.__init__.__doc__, 722 ) 723 if init_has_no_doc: 724 if inspect.isabstract(self.obj): 725 # Special case: We don't want to show constructors for abstract base classes unless 726 # they have a custom docstring. 727 del unsorted["__init__"] 728 elif issubclass(self.obj, enum.Enum): 729 # Special case: Do not show a constructor for enums. They are typically not constructed by users. 730 # The alternative would be showing __new__, as __call__ is too verbose. 731 del unsorted["__init__"] 732 elif issubclass(self.obj, dict): 733 # Special case: Do not show a constructor for dict subclasses. 734 unsorted.pop( 735 "__init__", None 736 ) # TypedDict subclasses may not have __init__. 737 else: 738 # Check if there's a helpful Metaclass.__call__ or Class.__new__. This dance is very similar to 739 # https://github.com/python/cpython/blob/9feae41c4f04ca27fd2c865807a5caeb50bf4fc4/Lib/inspect.py#L2359-L2376 740 call = _safe_getattr(type(self.obj), "__call__", None) 741 custom_call_with_custom_docstring = ( 742 call is not None 743 and not isinstance(call, NonUserDefinedCallables) 744 and call.__doc__ not in (None, object.__call__.__doc__) 745 ) 746 if custom_call_with_custom_docstring: 747 unsorted["__init__"] = call 748 else: 749 # Does our class define a custom __new__ method? 750 new = _safe_getattr(self.obj, "__new__", None) 751 custom_new_with_custom_docstring = ( 752 new is not None 753 and not isinstance(new, NonUserDefinedCallables) 754 and new.__doc__ not in (None, object.__new__.__doc__) 755 ) 756 if custom_new_with_custom_docstring: 757 unsorted["__init__"] = new 758 759 sorted: dict[str, Any] = {} 760 for cls in self._bases: 761 sorted, unsorted = doc_ast.sort_by_source(cls, sorted, unsorted) 762 sorted.update(unsorted) 763 return sorted 764 765 @cached_property 766 def bases(self) -> list[tuple[str, str, str]]: 767 """ 768 A list of all base classes, i.e. all immediate parent classes. 769 770 Each parent class is represented as a `(modulename, qualname, display_text)` tuple. 771 """ 772 bases = [] 773 for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__): 774 if x is object: 775 continue 776 o = get_origin(x) 777 if o: 778 bases.append((o.__module__, o.__qualname__, str(x))) 779 elif x.__module__ == self.modulename: 780 bases.append((x.__module__, x.__qualname__, x.__qualname__)) 781 else: 782 bases.append( 783 (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}") 784 ) 785 return bases 786 787 @cached_property 788 def decorators(self) -> list[str]: 789 """A list of all decorators the class is decorated with.""" 790 decorators = [] 791 for t in doc_ast.parse(self.obj).decorator_list: 792 decorators.append(f"@{doc_ast.unparse(t)}") 793 return decorators 794 795 @cached_property 796 def class_variables(self) -> list[Variable]: 797 """ 798 A list of all documented class variables in the class. 799 800 Class variables are variables that are explicitly annotated with `typing.ClassVar`. 801 All other variables are treated as instance variables. 802 """ 803 return [ 804 x 805 for x in self.members.values() 806 if isinstance(x, Variable) and x.is_classvar 807 ] 808 809 @cached_property 810 def instance_variables(self) -> list[Variable]: 811 """ 812 A list of all instance variables in the class. 813 """ 814 return [ 815 x 816 for x in self.members.values() 817 if isinstance(x, Variable) and not x.is_classvar 818 ] 819 820 @cached_property 821 def classmethods(self) -> list[Function]: 822 """ 823 A list of all documented `@classmethod`s. 824 """ 825 return [ 826 x 827 for x in self.members.values() 828 if isinstance(x, Function) and x.is_classmethod 829 ] 830 831 @cached_property 832 def staticmethods(self) -> list[Function]: 833 """ 834 A list of all documented `@staticmethod`s. 835 """ 836 return [ 837 x 838 for x in self.members.values() 839 if isinstance(x, Function) and x.is_staticmethod 840 ] 841 842 @cached_property 843 def methods(self) -> list[Function]: 844 """ 845 A list of all documented methods in the class that are neither static- nor classmethods. 846 """ 847 return [ 848 x 849 for x in self.members.values() 850 if isinstance(x, Function) 851 and not x.is_staticmethod 852 and not x.is_classmethod 853 ] 854 855 856if sys.version_info >= (3, 10): 857 WrappedFunction = types.FunctionType | staticmethod | classmethod 858else: # pragma: no cover 859 WrappedFunction = Union[types.FunctionType, staticmethod, classmethod] 860 861 862class Function(Doc[types.FunctionType]): 863 """ 864 Representation of a function's documentation. 865 866 This class covers all "flavors" of functions, for example it also 867 supports `@classmethod`s or `@staticmethod`s. 868 """ 869 870 kind = "function" 871 872 wrapped: WrappedFunction 873 """The original wrapped function (e.g., `staticmethod(func)`)""" 874 875 obj: types.FunctionType 876 """The unwrapped "real" function.""" 877 878 def __init__( 879 self, 880 modulename: str, 881 qualname: str, 882 func: WrappedFunction, 883 taken_from: tuple[str, str], 884 ): 885 """Initialize a function's documentation object.""" 886 unwrapped: types.FunctionType 887 if isinstance(func, (classmethod, staticmethod)): 888 unwrapped = func.__func__ # type: ignore 889 elif isinstance(func, singledispatchmethod): 890 unwrapped = func.func # type: ignore 891 elif hasattr(func, "__wrapped__"): 892 unwrapped = func.__wrapped__ 893 else: 894 unwrapped = func 895 super().__init__(modulename, qualname, unwrapped, taken_from) 896 self.wrapped = func 897 898 @cache 899 @_include_fullname_in_traceback 900 def __repr__(self): 901 if self.is_classmethod: 902 t = "class" 903 elif self.is_staticmethod: 904 t = "static" 905 elif self.qualname != _safe_getattr(self.obj, "__name__", None): 906 t = "method" 907 else: 908 t = "function" 909 return f"<{_decorators(self)}{t} {self.funcdef} {self.name}{self.signature}: ...{_docstr(self)}>" 910 911 @cached_property 912 def docstring(self) -> str: 913 doc = Doc.docstring.__get__(self) # type: ignore 914 if not doc: 915 # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors. 916 # We now do an ugly dance to obtain the bound object instead, 917 # that somewhat resembles what inspect._findclass is doing. 918 cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None) 919 for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]: 920 cls = _safe_getattr(cls, name, None) 921 922 unbound = _safe_getattr(cls, "__dict__", {}).get(self.name) 923 is_classmethod_property = isinstance(unbound, classmethod) and isinstance( 924 unbound.__func__, (property, cached_property) 925 ) 926 if not is_classmethod_property: 927 # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11. 928 # Directly accessing them would give us the return value, which has the wrong docstring. 929 doc = _safe_getdoc(_safe_getattr(cls, self.name, None)) 930 931 if doc == object.__init__.__doc__: 932 # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined... 933 return "" 934 else: 935 return doc 936 937 @cached_property 938 def is_classmethod(self) -> bool: 939 """ 940 `True` if this function is a `@classmethod`, `False` otherwise. 941 """ 942 return isinstance(self.wrapped, classmethod) 943 944 @cached_property 945 def is_staticmethod(self) -> bool: 946 """ 947 `True` if this function is a `@staticmethod`, `False` otherwise. 948 """ 949 return isinstance(self.wrapped, staticmethod) 950 951 @cached_property 952 def decorators(self) -> list[str]: 953 """A list of all decorators the function is decorated with.""" 954 decorators = [] 955 obj: types.FunctionType = self.obj # type: ignore 956 for t in doc_ast.parse(obj).decorator_list: 957 decorators.append(f"@{doc_ast.unparse(t)}") 958 return decorators 959 960 @cached_property 961 def funcdef(self) -> str: 962 """ 963 The string of keywords used to define the function, i.e. `"def"` or `"async def"`. 964 """ 965 if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction( 966 self.obj 967 ): 968 return "async def" 969 else: 970 return "def" 971 972 @cached_property 973 def signature(self) -> inspect.Signature: 974 """ 975 The function's signature. 976 977 This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature` 978 that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines 979 in repr(). Additionally, all types are already resolved. 980 981 If the signature cannot be determined, a placeholder Signature object is returned. 982 """ 983 if self.obj is object.__init__: 984 # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs) 985 # signature for the default __init__ method. 986 return inspect.Signature() 987 try: 988 sig = _PrettySignature.from_callable(self.obj) 989 except Exception: 990 return inspect.Signature( 991 [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)] 992 ) 993 mod = inspect.getmodule(self.obj) 994 globalns = _safe_getattr(mod, "__dict__", {}) 995 localns = globalns 996 for parent_cls_name in self.qualname.split(".")[:-1]: 997 parent_cls = localns.get(parent_cls_name, object) 998 localns = _safe_getattr(parent_cls, "__dict__", None) 999 if localns is None: 1000 break # pragma: no cover 1001 1002 if self.name == "__init__": 1003 sig = sig.replace(return_annotation=empty) 1004 else: 1005 sig = sig.replace( 1006 return_annotation=safe_eval_type( 1007 sig.return_annotation, globalns, localns, mod, self.fullname 1008 ) 1009 ) 1010 for p in sig.parameters.values(): 1011 p._annotation = safe_eval_type( # type: ignore 1012 p.annotation, globalns, localns, mod, self.fullname 1013 ) 1014 return sig 1015 1016 @cached_property 1017 def signature_without_self(self) -> inspect.Signature: 1018 """Like `signature`, but without the first argument. 1019 1020 This is useful to display constructors. 1021 """ 1022 return self.signature.replace( 1023 parameters=list(self.signature.parameters.values())[1:] 1024 ) 1025 1026 1027class Variable(Doc[None]): 1028 """ 1029 Representation of a variable's documentation. This includes module, class and instance variables. 1030 """ 1031 1032 kind = "variable" 1033 1034 default_value: ( 1035 Any | empty 1036 ) # technically Any includes empty, but this conveys intent. 1037 """ 1038 The variable's default value. 1039 1040 In some cases, no default value is known. This may either be because a variable is only defined in the constructor, 1041 or it is only declared with a type annotation without assignment (`foo: int`). 1042 To distinguish this case from a default value of `None`, `pdoc.doc_types.empty` is used as a placeholder. 1043 """ 1044 1045 annotation: type | empty 1046 """ 1047 The variable's type annotation. 1048 1049 If there is no type annotation, `pdoc.doc_types.empty` is used as a placeholder. 1050 """ 1051 1052 def __init__( 1053 self, 1054 modulename: str, 1055 qualname: str, 1056 *, 1057 taken_from: tuple[str, str], 1058 docstring: str, 1059 annotation: type | empty = empty, 1060 default_value: Any | empty = empty, 1061 ): 1062 """ 1063 Construct a variable doc object. 1064 1065 While classes and functions can introspect themselves to see their docstring, 1066 variables can't do that as we don't have a "variable object" we could query. 1067 As such, docstring, declaration location, type annotation, and the default value 1068 must be passed manually in the constructor. 1069 """ 1070 super().__init__(modulename, qualname, None, taken_from) 1071 # noinspection PyPropertyAccess 1072 self.docstring = inspect.cleandoc(docstring) 1073 self.annotation = annotation 1074 self.default_value = default_value 1075 1076 @cache 1077 @_include_fullname_in_traceback 1078 def __repr__(self): 1079 if self.default_value_str: 1080 default = f" = {self.default_value_str}" 1081 else: 1082 default = "" 1083 return f'<var {self.qualname.rsplit(".")[-1]}{self.annotation_str}{default}{_docstr(self)}>' 1084 1085 @cached_property 1086 def is_classvar(self) -> bool: 1087 """`True` if the variable is a class variable, `False` otherwise.""" 1088 if get_origin(self.annotation) is ClassVar: 1089 return True 1090 else: 1091 return False 1092 1093 @cached_property 1094 def is_typevar(self) -> bool: 1095 """`True` if the variable is a `typing.TypeVar`, `False` otherwise.""" 1096 if isinstance(self.default_value, TypeVar): 1097 return True 1098 else: 1099 return False 1100 1101 @cached_property 1102 def is_type_alias_type(self) -> bool: 1103 """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise.""" 1104 return isinstance(self.default_value, TypeAliasType) 1105 1106 @cached_property 1107 def is_enum_member(self) -> bool: 1108 """`True` if the variable is an enum member, `False` otherwise.""" 1109 if isinstance(self.default_value, enum.Enum): 1110 return True 1111 else: 1112 return False 1113 1114 @cached_property 1115 def default_value_str(self) -> str: 1116 """The variable's default value as a pretty-printed str.""" 1117 if self.default_value is empty: 1118 return "" 1119 if isinstance(self.default_value, TypeAliasType): 1120 return formatannotation(self.default_value.__value__) 1121 elif self.annotation == TypeAlias: 1122 return formatannotation(self.default_value) 1123 1124 # This is not perfect, but a solid attempt at preventing accidental leakage of secrets. 1125 # If you have input on how to improve the heuristic, please send a pull request! 1126 value_taken_from_env_var = ( 1127 isinstance(self.default_value, str) 1128 and len(self.default_value) >= 8 1129 and self.default_value in _environ_lookup() 1130 ) 1131 if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""): 1132 env_var = "$" + _environ_lookup()[self.default_value] 1133 warnings.warn( 1134 f"The default value of {self.fullname} matches the {env_var} environment variable. " 1135 f"To prevent accidental leakage of secrets, the default value is not displayed. " 1136 f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.", 1137 RuntimeWarning, 1138 ) 1139 return env_var 1140 1141 try: 1142 pretty = repr(self.default_value) 1143 except Exception as e: 1144 warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})") 1145 return "" 1146 1147 pretty = _remove_memory_addresses(pretty) 1148 return pretty 1149 1150 @cached_property 1151 def annotation_str(self) -> str: 1152 """The variable's type annotation as a pretty-printed str.""" 1153 if self.annotation is not empty: 1154 return f": {formatannotation(self.annotation)}" 1155 else: 1156 return "" 1157 1158 1159@cache 1160def _environ_lookup(): 1161 """ 1162 A reverse lookup of os.environ. This is a cached function so that it is evaluated lazily. 1163 """ 1164 return {value: key for key, value in os.environ.items()} 1165 1166 1167class _PrettySignature(inspect.Signature): 1168 """ 1169 A subclass of `inspect.Signature` that pads __str__ over several lines 1170 for complex signatures. 1171 """ 1172 1173 MULTILINE_CUTOFF = 70 1174 1175 def _params(self) -> list[str]: 1176 # redeclared here to keep code snipped below as-is. 1177 _POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY 1178 _VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL 1179 _KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY 1180 1181 # https://github.com/python/cpython/blob/799f8489d418b7f9207d333eac38214931bd7dcc/Lib/inspect.py#L3083-L3117 1182 # Change: added re.sub() to formatted = .... 1183 # ✂ start ✂ 1184 result = [] 1185 render_pos_only_separator = False 1186 render_kw_only_separator = True 1187 for param in self.parameters.values(): 1188 formatted = str(param) 1189 formatted = _remove_memory_addresses(formatted) 1190 1191 kind = param.kind 1192 1193 if kind == _POSITIONAL_ONLY: 1194 render_pos_only_separator = True 1195 elif render_pos_only_separator: 1196 # It's not a positional-only parameter, and the flag 1197 # is set to 'True' (there were pos-only params before.) 1198 result.append("/") 1199 render_pos_only_separator = False 1200 1201 if kind == _VAR_POSITIONAL: 1202 # OK, we have an '*args'-like parameter, so we won't need 1203 # a '*' to separate keyword-only arguments 1204 render_kw_only_separator = False 1205 elif kind == _KEYWORD_ONLY and render_kw_only_separator: 1206 # We have a keyword-only parameter to render and we haven't 1207 # rendered an '*args'-like parameter before, so add a '*' 1208 # separator to the parameters list ("foo(arg1, *, arg2)" case) 1209 result.append("*") 1210 # This condition should be only triggered once, so 1211 # reset the flag 1212 render_kw_only_separator = False 1213 1214 result.append(formatted) 1215 1216 if render_pos_only_separator: 1217 # There were only positional-only parameters, hence the 1218 # flag was not reset to 'False' 1219 result.append("/") 1220 # ✂ end ✂ 1221 1222 return result 1223 1224 def _return_annotation_str(self) -> str: 1225 if self.return_annotation is not empty: 1226 return formatannotation(self.return_annotation) 1227 else: 1228 return "" 1229 1230 def __str__(self): 1231 result = self._params() 1232 return_annot = self._return_annotation_str() 1233 1234 total_len = sum(len(x) + 2 for x in result) + len(return_annot) 1235 1236 if total_len > self.MULTILINE_CUTOFF: 1237 rendered = "(\n " + ",\n ".join(result) + "\n)" 1238 else: 1239 rendered = "({})".format(", ".join(result)) 1240 if return_annot: 1241 rendered += f" -> {return_annot}" 1242 1243 return rendered 1244 1245 1246def _cut(x: str) -> str: 1247 """helper function for Doc.__repr__()""" 1248 if len(x) < 20: 1249 return x 1250 else: 1251 return x[:20] + "…" 1252 1253 1254def _docstr(doc: Doc) -> str: 1255 """helper function for Doc.__repr__()""" 1256 docstr = [] 1257 if doc.is_inherited: 1258 docstr.append(f"inherited from {'.'.join(doc.taken_from).rstrip('.')}") 1259 if doc.docstring: 1260 docstr.append(_cut(doc.docstring)) 1261 if docstr: 1262 return f" # {', '.join(docstr)}" 1263 else: 1264 return "" 1265 1266 1267def _decorators(doc: Class | Function) -> str: 1268 """helper function for Doc.__repr__()""" 1269 if doc.decorators: 1270 return " ".join(doc.decorators) + " " 1271 else: 1272 return "" 1273 1274 1275def _children(doc: Namespace) -> str: 1276 children = "\n".join( 1277 repr(x) 1278 for x in doc.members.values() 1279 if not x.name.startswith("_") or x.name == "__init__" 1280 ) 1281 if children: 1282 children += "\n" 1283 children = f"\n{textwrap.indent(children, ' ')}" 1284 return children 1285 1286 1287def _safe_getattr(obj, attr, default): 1288 """Like `getattr()`, but never raises.""" 1289 try: 1290 return getattr(obj, attr, default) 1291 except Exception as e: 1292 warnings.warn( 1293 f"getattr({obj!r}, {attr!r}, {default!r}) raised an exception: {e!r}" 1294 ) 1295 return default 1296 1297 1298def _safe_getdoc(obj: Any) -> str: 1299 """Like `inspect.getdoc()`, but never raises. Always returns a stripped string.""" 1300 try: 1301 doc = inspect.getdoc(obj) or "" 1302 except Exception as e: 1303 warnings.warn(f"inspect.getdoc({obj!r}) raised an exception: {e!r}") 1304 return "" 1305 else: 1306 return doc.strip() 1307 1308 1309def _remove_memory_addresses(x: str) -> str: 1310 """Remove memory addresses from repr() output""" 1311 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 is_dataclass_with_default_docstring = ( 592 dataclasses.is_dataclass(self.obj) 593 # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py 594 and doc 595 == self.obj.__name__ 596 + str(inspect.signature(self.obj)).replace(" -> None", "") 597 ) 598 if is_dataclass_with_default_docstring: 599 return "" 600 return doc 601 602 @cached_property 603 def _var_docstrings(self) -> dict[str, str]: 604 docstrings: dict[str, str] = {} 605 for cls in self._bases: 606 for name, docstr in doc_ast.walk_tree(cls).var_docstrings.items(): 607 docstrings.setdefault(name, docstr) 608 return docstrings 609 610 @cached_property 611 def _func_docstrings(self) -> dict[str, str]: 612 docstrings: dict[str, str] = {} 613 for cls in self._bases: 614 for name, docstr in doc_ast.walk_tree(cls).func_docstrings.items(): 615 docstrings.setdefault(name, docstr) 616 return docstrings 617 618 @cached_property 619 def _var_annotations(self) -> dict[str, type]: 620 # this is a bit tricky: __annotations__ also includes annotations from parent classes, 621 # but we need to execute them in the namespace of the parent class. 622 # Our workaround for this is to walk the MRO backwards, and only update/evaluate only if the annotation changes. 623 annotations: dict[ 624 str, tuple[Any, type] 625 ] = {} # attribute -> (annotation_unresolved, annotation_resolved) 626 for cls in reversed(self._bases): 627 cls_annotations = doc_ast.walk_tree(cls).annotations.copy() 628 dynamic_annotations = _safe_getattr(cls, "__annotations__", None) 629 if isinstance(dynamic_annotations, dict): 630 for attr, unresolved_annotation in dynamic_annotations.items(): 631 cls_annotations[attr] = unresolved_annotation 632 cls_fullname = ( 633 _safe_getattr(cls, "__module__", "") + "." + cls.__qualname__ 634 ).lstrip(".") 635 636 new_annotations = { 637 attr: unresolved_annotation 638 for attr, unresolved_annotation in cls_annotations.items() 639 if attr not in annotations 640 or annotations[attr][0] is not unresolved_annotation 641 } 642 localns = _safe_getattr(cls, "__dict__", None) 643 for attr, t in resolve_annotations( 644 new_annotations, inspect.getmodule(cls), localns, cls_fullname 645 ).items(): 646 annotations[attr] = (new_annotations[attr], t) 647 648 return {k: v[1] for k, v in annotations.items()} 649 650 @cached_property 651 def _bases(self) -> tuple[type, ...]: 652 orig_bases = _safe_getattr(self.obj, "__orig_bases__", ()) 653 old_python_typeddict_workaround = ( 654 sys.version_info < (3, 12) 655 and orig_bases 656 and _safe_getattr(orig_bases[-1], "__name__", None) == "TypedDict" 657 ) 658 if old_python_typeddict_workaround: # pragma: no cover 659 # TypedDicts on Python <3.12 have a botched __mro__. We need to fix it. 660 return (self.obj, *orig_bases[:-1]) 661 662 # __mro__ and __orig_bases__ differ between Python versions and special cases like TypedDict/NamedTuple. 663 # This here is a pragmatic approximation of what we want. 664 return ( 665 *(base for base in orig_bases if isinstance(base, type)), 666 *self.obj.__mro__, 667 ) 668 669 @cached_property 670 def _declarations(self) -> dict[str, tuple[str, str]]: 671 decls: dict[str, tuple[str, str]] = {} 672 for cls in self._bases: 673 treeinfo = doc_ast.walk_tree(cls) 674 for name in ( 675 treeinfo.var_docstrings.keys() 676 | treeinfo.func_docstrings.keys() 677 | treeinfo.annotations.keys() 678 ): 679 decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}")) 680 for name in cls.__dict__: 681 decls.setdefault(name, (cls.__module__, f"{cls.__qualname__}.{name}")) 682 if decls.get("__init__", None) == ("builtins", "object.__init__"): 683 decls["__init__"] = ( 684 self.obj.__module__, 685 f"{self.obj.__qualname__}.__init__", 686 ) 687 return decls 688 689 def _taken_from(self, member_name: str, obj: Any) -> tuple[str, str]: 690 try: 691 return self._declarations[member_name] 692 except KeyError: # pragma: no cover 693 # TypedDict botches __mro__ on Python <3.12 and may need special casing here. 694 # One workaround is to also specify TypedDict as a base class, see pdoc.doc.Class._bases. 695 warnings.warn( 696 f"Cannot determine where {self.fullname}.{member_name} is taken from, assuming current file." 697 ) 698 return self.modulename, f"{self.qualname}.{member_name}" 699 700 @cached_property 701 def own_members(self) -> list[Doc]: 702 members = self._members_by_origin.get((self.modulename, self.qualname), []) 703 if self.taken_from != (self.modulename, self.qualname): 704 # .taken_from may be != (self.modulename, self.qualname), for example when 705 # a module re-exports a class from a private submodule. 706 members += self._members_by_origin.get(self.taken_from, []) 707 return members 708 709 @cached_property 710 def _member_objects(self) -> dict[str, Any]: 711 unsorted: dict[str, Any] = {} 712 for cls in self._bases: 713 for name, obj in cls.__dict__.items(): 714 unsorted.setdefault(name, obj) 715 for name in self._var_docstrings: 716 unsorted.setdefault(name, empty) 717 for name in self._var_annotations: 718 unsorted.setdefault(name, empty) 719 720 init_has_no_doc = unsorted.get("__init__", object.__init__).__doc__ in ( 721 None, 722 object.__init__.__doc__, 723 ) 724 if init_has_no_doc: 725 if inspect.isabstract(self.obj): 726 # Special case: We don't want to show constructors for abstract base classes unless 727 # they have a custom docstring. 728 del unsorted["__init__"] 729 elif issubclass(self.obj, enum.Enum): 730 # Special case: Do not show a constructor for enums. They are typically not constructed by users. 731 # The alternative would be showing __new__, as __call__ is too verbose. 732 del unsorted["__init__"] 733 elif issubclass(self.obj, dict): 734 # Special case: Do not show a constructor for dict subclasses. 735 unsorted.pop( 736 "__init__", None 737 ) # TypedDict subclasses may not have __init__. 738 else: 739 # Check if there's a helpful Metaclass.__call__ or Class.__new__. This dance is very similar to 740 # https://github.com/python/cpython/blob/9feae41c4f04ca27fd2c865807a5caeb50bf4fc4/Lib/inspect.py#L2359-L2376 741 call = _safe_getattr(type(self.obj), "__call__", None) 742 custom_call_with_custom_docstring = ( 743 call is not None 744 and not isinstance(call, NonUserDefinedCallables) 745 and call.__doc__ not in (None, object.__call__.__doc__) 746 ) 747 if custom_call_with_custom_docstring: 748 unsorted["__init__"] = call 749 else: 750 # Does our class define a custom __new__ method? 751 new = _safe_getattr(self.obj, "__new__", None) 752 custom_new_with_custom_docstring = ( 753 new is not None 754 and not isinstance(new, NonUserDefinedCallables) 755 and new.__doc__ not in (None, object.__new__.__doc__) 756 ) 757 if custom_new_with_custom_docstring: 758 unsorted["__init__"] = new 759 760 sorted: dict[str, Any] = {} 761 for cls in self._bases: 762 sorted, unsorted = doc_ast.sort_by_source(cls, sorted, unsorted) 763 sorted.update(unsorted) 764 return sorted 765 766 @cached_property 767 def bases(self) -> list[tuple[str, str, str]]: 768 """ 769 A list of all base classes, i.e. all immediate parent classes. 770 771 Each parent class is represented as a `(modulename, qualname, display_text)` tuple. 772 """ 773 bases = [] 774 for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__): 775 if x is object: 776 continue 777 o = get_origin(x) 778 if o: 779 bases.append((o.__module__, o.__qualname__, str(x))) 780 elif x.__module__ == self.modulename: 781 bases.append((x.__module__, x.__qualname__, x.__qualname__)) 782 else: 783 bases.append( 784 (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}") 785 ) 786 return bases 787 788 @cached_property 789 def decorators(self) -> list[str]: 790 """A list of all decorators the class is decorated with.""" 791 decorators = [] 792 for t in doc_ast.parse(self.obj).decorator_list: 793 decorators.append(f"@{doc_ast.unparse(t)}") 794 return decorators 795 796 @cached_property 797 def class_variables(self) -> list[Variable]: 798 """ 799 A list of all documented class variables in the class. 800 801 Class variables are variables that are explicitly annotated with `typing.ClassVar`. 802 All other variables are treated as instance variables. 803 """ 804 return [ 805 x 806 for x in self.members.values() 807 if isinstance(x, Variable) and x.is_classvar 808 ] 809 810 @cached_property 811 def instance_variables(self) -> list[Variable]: 812 """ 813 A list of all instance variables in the class. 814 """ 815 return [ 816 x 817 for x in self.members.values() 818 if isinstance(x, Variable) and not x.is_classvar 819 ] 820 821 @cached_property 822 def classmethods(self) -> list[Function]: 823 """ 824 A list of all documented `@classmethod`s. 825 """ 826 return [ 827 x 828 for x in self.members.values() 829 if isinstance(x, Function) and x.is_classmethod 830 ] 831 832 @cached_property 833 def staticmethods(self) -> list[Function]: 834 """ 835 A list of all documented `@staticmethod`s. 836 """ 837 return [ 838 x 839 for x in self.members.values() 840 if isinstance(x, Function) and x.is_staticmethod 841 ] 842 843 @cached_property 844 def methods(self) -> list[Function]: 845 """ 846 A list of all documented methods in the class that are neither static- nor classmethods. 847 """ 848 return [ 849 x 850 for x in self.members.values() 851 if isinstance(x, Function) 852 and not x.is_staticmethod 853 and not x.is_classmethod 854 ]
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 is_dataclass_with_default_docstring = ( 592 dataclasses.is_dataclass(self.obj) 593 # from https://github.com/python/cpython/blob/3.10/Lib/dataclasses.py 594 and doc 595 == self.obj.__name__ 596 + str(inspect.signature(self.obj)).replace(" -> None", "") 597 ) 598 if is_dataclass_with_default_docstring: 599 return "" 600 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.
700 @cached_property 701 def own_members(self) -> list[Doc]: 702 members = self._members_by_origin.get((self.modulename, self.qualname), []) 703 if self.taken_from != (self.modulename, self.qualname): 704 # .taken_from may be != (self.modulename, self.qualname), for example when 705 # a module re-exports a class from a private submodule. 706 members += self._members_by_origin.get(self.taken_from, []) 707 return members
A list of all own (i.e. non-inherited) members
.
766 @cached_property 767 def bases(self) -> list[tuple[str, str, str]]: 768 """ 769 A list of all base classes, i.e. all immediate parent classes. 770 771 Each parent class is represented as a `(modulename, qualname, display_text)` tuple. 772 """ 773 bases = [] 774 for x in _safe_getattr(self.obj, "__orig_bases__", self.obj.__bases__): 775 if x is object: 776 continue 777 o = get_origin(x) 778 if o: 779 bases.append((o.__module__, o.__qualname__, str(x))) 780 elif x.__module__ == self.modulename: 781 bases.append((x.__module__, x.__qualname__, x.__qualname__)) 782 else: 783 bases.append( 784 (x.__module__, x.__qualname__, f"{x.__module__}.{x.__qualname__}") 785 ) 786 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.
788 @cached_property 789 def decorators(self) -> list[str]: 790 """A list of all decorators the class is decorated with.""" 791 decorators = [] 792 for t in doc_ast.parse(self.obj).decorator_list: 793 decorators.append(f"@{doc_ast.unparse(t)}") 794 return decorators
A list of all decorators the class is decorated with.
796 @cached_property 797 def class_variables(self) -> list[Variable]: 798 """ 799 A list of all documented class variables in the class. 800 801 Class variables are variables that are explicitly annotated with `typing.ClassVar`. 802 All other variables are treated as instance variables. 803 """ 804 return [ 805 x 806 for x in self.members.values() 807 if isinstance(x, Variable) and x.is_classvar 808 ]
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.
810 @cached_property 811 def instance_variables(self) -> list[Variable]: 812 """ 813 A list of all instance variables in the class. 814 """ 815 return [ 816 x 817 for x in self.members.values() 818 if isinstance(x, Variable) and not x.is_classvar 819 ]
A list of all instance variables in the class.
821 @cached_property 822 def classmethods(self) -> list[Function]: 823 """ 824 A list of all documented `@classmethod`s. 825 """ 826 return [ 827 x 828 for x in self.members.values() 829 if isinstance(x, Function) and x.is_classmethod 830 ]
A list of all documented @classmethod
s.
832 @cached_property 833 def staticmethods(self) -> list[Function]: 834 """ 835 A list of all documented `@staticmethod`s. 836 """ 837 return [ 838 x 839 for x in self.members.values() 840 if isinstance(x, Function) and x.is_staticmethod 841 ]
A list of all documented @staticmethod
s.
843 @cached_property 844 def methods(self) -> list[Function]: 845 """ 846 A list of all documented methods in the class that are neither static- nor classmethods. 847 """ 848 return [ 849 x 850 for x in self.members.values() 851 if isinstance(x, Function) 852 and not x.is_staticmethod 853 and not x.is_classmethod 854 ]
A list of all documented methods in the class that are neither static- nor classmethods.
863class Function(Doc[types.FunctionType]): 864 """ 865 Representation of a function's documentation. 866 867 This class covers all "flavors" of functions, for example it also 868 supports `@classmethod`s or `@staticmethod`s. 869 """ 870 871 kind = "function" 872 873 wrapped: WrappedFunction 874 """The original wrapped function (e.g., `staticmethod(func)`)""" 875 876 obj: types.FunctionType 877 """The unwrapped "real" function.""" 878 879 def __init__( 880 self, 881 modulename: str, 882 qualname: str, 883 func: WrappedFunction, 884 taken_from: tuple[str, str], 885 ): 886 """Initialize a function's documentation object.""" 887 unwrapped: types.FunctionType 888 if isinstance(func, (classmethod, staticmethod)): 889 unwrapped = func.__func__ # type: ignore 890 elif isinstance(func, singledispatchmethod): 891 unwrapped = func.func # type: ignore 892 elif hasattr(func, "__wrapped__"): 893 unwrapped = func.__wrapped__ 894 else: 895 unwrapped = func 896 super().__init__(modulename, qualname, unwrapped, taken_from) 897 self.wrapped = func 898 899 @cache 900 @_include_fullname_in_traceback 901 def __repr__(self): 902 if self.is_classmethod: 903 t = "class" 904 elif self.is_staticmethod: 905 t = "static" 906 elif self.qualname != _safe_getattr(self.obj, "__name__", None): 907 t = "method" 908 else: 909 t = "function" 910 return f"<{_decorators(self)}{t} {self.funcdef} {self.name}{self.signature}: ...{_docstr(self)}>" 911 912 @cached_property 913 def docstring(self) -> str: 914 doc = Doc.docstring.__get__(self) # type: ignore 915 if not doc: 916 # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors. 917 # We now do an ugly dance to obtain the bound object instead, 918 # that somewhat resembles what inspect._findclass is doing. 919 cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None) 920 for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]: 921 cls = _safe_getattr(cls, name, None) 922 923 unbound = _safe_getattr(cls, "__dict__", {}).get(self.name) 924 is_classmethod_property = isinstance(unbound, classmethod) and isinstance( 925 unbound.__func__, (property, cached_property) 926 ) 927 if not is_classmethod_property: 928 # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11. 929 # Directly accessing them would give us the return value, which has the wrong docstring. 930 doc = _safe_getdoc(_safe_getattr(cls, self.name, None)) 931 932 if doc == object.__init__.__doc__: 933 # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined... 934 return "" 935 else: 936 return doc 937 938 @cached_property 939 def is_classmethod(self) -> bool: 940 """ 941 `True` if this function is a `@classmethod`, `False` otherwise. 942 """ 943 return isinstance(self.wrapped, classmethod) 944 945 @cached_property 946 def is_staticmethod(self) -> bool: 947 """ 948 `True` if this function is a `@staticmethod`, `False` otherwise. 949 """ 950 return isinstance(self.wrapped, staticmethod) 951 952 @cached_property 953 def decorators(self) -> list[str]: 954 """A list of all decorators the function is decorated with.""" 955 decorators = [] 956 obj: types.FunctionType = self.obj # type: ignore 957 for t in doc_ast.parse(obj).decorator_list: 958 decorators.append(f"@{doc_ast.unparse(t)}") 959 return decorators 960 961 @cached_property 962 def funcdef(self) -> str: 963 """ 964 The string of keywords used to define the function, i.e. `"def"` or `"async def"`. 965 """ 966 if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction( 967 self.obj 968 ): 969 return "async def" 970 else: 971 return "def" 972 973 @cached_property 974 def signature(self) -> inspect.Signature: 975 """ 976 The function's signature. 977 978 This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature` 979 that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines 980 in repr(). Additionally, all types are already resolved. 981 982 If the signature cannot be determined, a placeholder Signature object is returned. 983 """ 984 if self.obj is object.__init__: 985 # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs) 986 # signature for the default __init__ method. 987 return inspect.Signature() 988 try: 989 sig = _PrettySignature.from_callable(self.obj) 990 except Exception: 991 return inspect.Signature( 992 [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)] 993 ) 994 mod = inspect.getmodule(self.obj) 995 globalns = _safe_getattr(mod, "__dict__", {}) 996 localns = globalns 997 for parent_cls_name in self.qualname.split(".")[:-1]: 998 parent_cls = localns.get(parent_cls_name, object) 999 localns = _safe_getattr(parent_cls, "__dict__", None) 1000 if localns is None: 1001 break # pragma: no cover 1002 1003 if self.name == "__init__": 1004 sig = sig.replace(return_annotation=empty) 1005 else: 1006 sig = sig.replace( 1007 return_annotation=safe_eval_type( 1008 sig.return_annotation, globalns, localns, mod, self.fullname 1009 ) 1010 ) 1011 for p in sig.parameters.values(): 1012 p._annotation = safe_eval_type( # type: ignore 1013 p.annotation, globalns, localns, mod, self.fullname 1014 ) 1015 return sig 1016 1017 @cached_property 1018 def signature_without_self(self) -> inspect.Signature: 1019 """Like `signature`, but without the first argument. 1020 1021 This is useful to display constructors. 1022 """ 1023 return self.signature.replace( 1024 parameters=list(self.signature.parameters.values())[1:] 1025 )
Representation of a function's documentation.
This class covers all "flavors" of functions, for example it also
supports @classmethod
s or @staticmethod
s.
879 def __init__( 880 self, 881 modulename: str, 882 qualname: str, 883 func: WrappedFunction, 884 taken_from: tuple[str, str], 885 ): 886 """Initialize a function's documentation object.""" 887 unwrapped: types.FunctionType 888 if isinstance(func, (classmethod, staticmethod)): 889 unwrapped = func.__func__ # type: ignore 890 elif isinstance(func, singledispatchmethod): 891 unwrapped = func.func # type: ignore 892 elif hasattr(func, "__wrapped__"): 893 unwrapped = func.__wrapped__ 894 else: 895 unwrapped = func 896 super().__init__(modulename, qualname, unwrapped, taken_from) 897 self.wrapped = func
Initialize a function's documentation object.
The original wrapped function (e.g., staticmethod(func)
)
912 @cached_property 913 def docstring(self) -> str: 914 doc = Doc.docstring.__get__(self) # type: ignore 915 if not doc: 916 # inspect.getdoc fails for inherited @classmethods and unbound @property descriptors. 917 # We now do an ugly dance to obtain the bound object instead, 918 # that somewhat resembles what inspect._findclass is doing. 919 cls = sys.modules.get(_safe_getattr(self.obj, "__module__", None), None) 920 for name in _safe_getattr(self.obj, "__qualname__", "").split(".")[:-1]: 921 cls = _safe_getattr(cls, name, None) 922 923 unbound = _safe_getattr(cls, "__dict__", {}).get(self.name) 924 is_classmethod_property = isinstance(unbound, classmethod) and isinstance( 925 unbound.__func__, (property, cached_property) 926 ) 927 if not is_classmethod_property: 928 # We choke on @classmethod @property, but that's okay because it's been deprecated with Python 3.11. 929 # Directly accessing them would give us the return value, which has the wrong docstring. 930 doc = _safe_getdoc(_safe_getattr(cls, self.name, None)) 931 932 if doc == object.__init__.__doc__: 933 # inspect.getdoc(Foo.__init__) returns the docstring, for object.__init__ if left undefined... 934 return "" 935 else: 936 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.
938 @cached_property 939 def is_classmethod(self) -> bool: 940 """ 941 `True` if this function is a `@classmethod`, `False` otherwise. 942 """ 943 return isinstance(self.wrapped, classmethod)
True
if this function is a @classmethod
, False
otherwise.
945 @cached_property 946 def is_staticmethod(self) -> bool: 947 """ 948 `True` if this function is a `@staticmethod`, `False` otherwise. 949 """ 950 return isinstance(self.wrapped, staticmethod)
True
if this function is a @staticmethod
, False
otherwise.
952 @cached_property 953 def decorators(self) -> list[str]: 954 """A list of all decorators the function is decorated with.""" 955 decorators = [] 956 obj: types.FunctionType = self.obj # type: ignore 957 for t in doc_ast.parse(obj).decorator_list: 958 decorators.append(f"@{doc_ast.unparse(t)}") 959 return decorators
A list of all decorators the function is decorated with.
961 @cached_property 962 def funcdef(self) -> str: 963 """ 964 The string of keywords used to define the function, i.e. `"def"` or `"async def"`. 965 """ 966 if inspect.iscoroutinefunction(self.obj) or inspect.isasyncgenfunction( 967 self.obj 968 ): 969 return "async def" 970 else: 971 return "def"
The string of keywords used to define the function, i.e. "def"
or "async def"
.
973 @cached_property 974 def signature(self) -> inspect.Signature: 975 """ 976 The function's signature. 977 978 This usually returns an instance of `_PrettySignature`, a subclass of `inspect.Signature` 979 that contains pdoc-specific optimizations. For example, long argument lists are split over multiple lines 980 in repr(). Additionally, all types are already resolved. 981 982 If the signature cannot be determined, a placeholder Signature object is returned. 983 """ 984 if self.obj is object.__init__: 985 # there is a weird edge case were inspect.signature returns a confusing (self, /, *args, **kwargs) 986 # signature for the default __init__ method. 987 return inspect.Signature() 988 try: 989 sig = _PrettySignature.from_callable(self.obj) 990 except Exception: 991 return inspect.Signature( 992 [inspect.Parameter("unknown", inspect.Parameter.POSITIONAL_OR_KEYWORD)] 993 ) 994 mod = inspect.getmodule(self.obj) 995 globalns = _safe_getattr(mod, "__dict__", {}) 996 localns = globalns 997 for parent_cls_name in self.qualname.split(".")[:-1]: 998 parent_cls = localns.get(parent_cls_name, object) 999 localns = _safe_getattr(parent_cls, "__dict__", None) 1000 if localns is None: 1001 break # pragma: no cover 1002 1003 if self.name == "__init__": 1004 sig = sig.replace(return_annotation=empty) 1005 else: 1006 sig = sig.replace( 1007 return_annotation=safe_eval_type( 1008 sig.return_annotation, globalns, localns, mod, self.fullname 1009 ) 1010 ) 1011 for p in sig.parameters.values(): 1012 p._annotation = safe_eval_type( # type: ignore 1013 p.annotation, globalns, localns, mod, self.fullname 1014 ) 1015 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.
1017 @cached_property 1018 def signature_without_self(self) -> inspect.Signature: 1019 """Like `signature`, but without the first argument. 1020 1021 This is useful to display constructors. 1022 """ 1023 return self.signature.replace( 1024 parameters=list(self.signature.parameters.values())[1:] 1025 )
Like signature
, but without the first argument.
This is useful to display constructors.
Inherited Members
1028class Variable(Doc[None]): 1029 """ 1030 Representation of a variable's documentation. This includes module, class and instance variables. 1031 """ 1032 1033 kind = "variable" 1034 1035 default_value: ( 1036 Any | empty 1037 ) # technically Any includes empty, but this conveys intent. 1038 """ 1039 The variable's default value. 1040 1041 In some cases, no default value is known. This may either be because a variable is only defined in the constructor, 1042 or it is only declared with a type annotation without assignment (`foo: int`). 1043 To distinguish this case from a default value of `None`, `pdoc.doc_types.empty` is used as a placeholder. 1044 """ 1045 1046 annotation: type | empty 1047 """ 1048 The variable's type annotation. 1049 1050 If there is no type annotation, `pdoc.doc_types.empty` is used as a placeholder. 1051 """ 1052 1053 def __init__( 1054 self, 1055 modulename: str, 1056 qualname: str, 1057 *, 1058 taken_from: tuple[str, str], 1059 docstring: str, 1060 annotation: type | empty = empty, 1061 default_value: Any | empty = empty, 1062 ): 1063 """ 1064 Construct a variable doc object. 1065 1066 While classes and functions can introspect themselves to see their docstring, 1067 variables can't do that as we don't have a "variable object" we could query. 1068 As such, docstring, declaration location, type annotation, and the default value 1069 must be passed manually in the constructor. 1070 """ 1071 super().__init__(modulename, qualname, None, taken_from) 1072 # noinspection PyPropertyAccess 1073 self.docstring = inspect.cleandoc(docstring) 1074 self.annotation = annotation 1075 self.default_value = default_value 1076 1077 @cache 1078 @_include_fullname_in_traceback 1079 def __repr__(self): 1080 if self.default_value_str: 1081 default = f" = {self.default_value_str}" 1082 else: 1083 default = "" 1084 return f'<var {self.qualname.rsplit(".")[-1]}{self.annotation_str}{default}{_docstr(self)}>' 1085 1086 @cached_property 1087 def is_classvar(self) -> bool: 1088 """`True` if the variable is a class variable, `False` otherwise.""" 1089 if get_origin(self.annotation) is ClassVar: 1090 return True 1091 else: 1092 return False 1093 1094 @cached_property 1095 def is_typevar(self) -> bool: 1096 """`True` if the variable is a `typing.TypeVar`, `False` otherwise.""" 1097 if isinstance(self.default_value, TypeVar): 1098 return True 1099 else: 1100 return False 1101 1102 @cached_property 1103 def is_type_alias_type(self) -> bool: 1104 """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise.""" 1105 return isinstance(self.default_value, TypeAliasType) 1106 1107 @cached_property 1108 def is_enum_member(self) -> bool: 1109 """`True` if the variable is an enum member, `False` otherwise.""" 1110 if isinstance(self.default_value, enum.Enum): 1111 return True 1112 else: 1113 return False 1114 1115 @cached_property 1116 def default_value_str(self) -> str: 1117 """The variable's default value as a pretty-printed str.""" 1118 if self.default_value is empty: 1119 return "" 1120 if isinstance(self.default_value, TypeAliasType): 1121 return formatannotation(self.default_value.__value__) 1122 elif self.annotation == TypeAlias: 1123 return formatannotation(self.default_value) 1124 1125 # This is not perfect, but a solid attempt at preventing accidental leakage of secrets. 1126 # If you have input on how to improve the heuristic, please send a pull request! 1127 value_taken_from_env_var = ( 1128 isinstance(self.default_value, str) 1129 and len(self.default_value) >= 8 1130 and self.default_value in _environ_lookup() 1131 ) 1132 if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""): 1133 env_var = "$" + _environ_lookup()[self.default_value] 1134 warnings.warn( 1135 f"The default value of {self.fullname} matches the {env_var} environment variable. " 1136 f"To prevent accidental leakage of secrets, the default value is not displayed. " 1137 f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.", 1138 RuntimeWarning, 1139 ) 1140 return env_var 1141 1142 try: 1143 pretty = repr(self.default_value) 1144 except Exception as e: 1145 warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})") 1146 return "" 1147 1148 pretty = _remove_memory_addresses(pretty) 1149 return pretty 1150 1151 @cached_property 1152 def annotation_str(self) -> str: 1153 """The variable's type annotation as a pretty-printed str.""" 1154 if self.annotation is not empty: 1155 return f": {formatannotation(self.annotation)}" 1156 else: 1157 return ""
Representation of a variable's documentation. This includes module, class and instance variables.
1053 def __init__( 1054 self, 1055 modulename: str, 1056 qualname: str, 1057 *, 1058 taken_from: tuple[str, str], 1059 docstring: str, 1060 annotation: type | empty = empty, 1061 default_value: Any | empty = empty, 1062 ): 1063 """ 1064 Construct a variable doc object. 1065 1066 While classes and functions can introspect themselves to see their docstring, 1067 variables can't do that as we don't have a "variable object" we could query. 1068 As such, docstring, declaration location, type annotation, and the default value 1069 must be passed manually in the constructor. 1070 """ 1071 super().__init__(modulename, qualname, None, taken_from) 1072 # noinspection PyPropertyAccess 1073 self.docstring = inspect.cleandoc(docstring) 1074 self.annotation = annotation 1075 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.
1086 @cached_property 1087 def is_classvar(self) -> bool: 1088 """`True` if the variable is a class variable, `False` otherwise.""" 1089 if get_origin(self.annotation) is ClassVar: 1090 return True 1091 else: 1092 return False
True
if the variable is a class variable, False
otherwise.
1094 @cached_property 1095 def is_typevar(self) -> bool: 1096 """`True` if the variable is a `typing.TypeVar`, `False` otherwise.""" 1097 if isinstance(self.default_value, TypeVar): 1098 return True 1099 else: 1100 return False
True
if the variable is a typing.TypeVar
, False
otherwise.
1102 @cached_property 1103 def is_type_alias_type(self) -> bool: 1104 """`True` if the variable is a `typing.TypeAliasType`, `False` otherwise.""" 1105 return isinstance(self.default_value, TypeAliasType)
True
if the variable is a typing.TypeAliasType
, False
otherwise.
1107 @cached_property 1108 def is_enum_member(self) -> bool: 1109 """`True` if the variable is an enum member, `False` otherwise.""" 1110 if isinstance(self.default_value, enum.Enum): 1111 return True 1112 else: 1113 return False
True
if the variable is an enum member, False
otherwise.
1115 @cached_property 1116 def default_value_str(self) -> str: 1117 """The variable's default value as a pretty-printed str.""" 1118 if self.default_value is empty: 1119 return "" 1120 if isinstance(self.default_value, TypeAliasType): 1121 return formatannotation(self.default_value.__value__) 1122 elif self.annotation == TypeAlias: 1123 return formatannotation(self.default_value) 1124 1125 # This is not perfect, but a solid attempt at preventing accidental leakage of secrets. 1126 # If you have input on how to improve the heuristic, please send a pull request! 1127 value_taken_from_env_var = ( 1128 isinstance(self.default_value, str) 1129 and len(self.default_value) >= 8 1130 and self.default_value in _environ_lookup() 1131 ) 1132 if value_taken_from_env_var and not os.environ.get("PDOC_DISPLAY_ENV_VARS", ""): 1133 env_var = "$" + _environ_lookup()[self.default_value] 1134 warnings.warn( 1135 f"The default value of {self.fullname} matches the {env_var} environment variable. " 1136 f"To prevent accidental leakage of secrets, the default value is not displayed. " 1137 f"Disable this behavior by setting PDOC_DISPLAY_ENV_VARS=1 as an environment variable.", 1138 RuntimeWarning, 1139 ) 1140 return env_var 1141 1142 try: 1143 pretty = repr(self.default_value) 1144 except Exception as e: 1145 warnings.warn(f"repr({self.fullname}) raised an exception ({e!r})") 1146 return "" 1147 1148 pretty = _remove_memory_addresses(pretty) 1149 return pretty
The variable's default value as a pretty-printed str.
1151 @cached_property 1152 def annotation_str(self) -> str: 1153 """The variable's type annotation as a pretty-printed str.""" 1154 if self.annotation is not empty: 1155 return f": {formatannotation(self.annotation)}" 1156 else: 1157 return ""
The variable's type annotation as a pretty-printed str.