Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pdoc/__init__.py: 18%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Python package `pdoc` provides types, functions, and a command-line
3interface for accessing public documentation of Python modules, and
4for presenting it in a user-friendly, industry-standard open format.
5It is best suited for small- to medium-sized projects with tidy,
6hierarchical APIs.
8.. include:: ./documentation.md
9"""
10import ast
11import enum
12import importlib.machinery
13import importlib.util
14import inspect
15import os
16import os.path as path
17import re
18import sys
19import typing
20from contextlib import contextmanager
21from copy import copy
22from dataclasses import is_dataclass
23from functools import cached_property, lru_cache, reduce, partial, wraps
24from itertools import tee, groupby
25from types import FunctionType, ModuleType
26from typing import ( # noqa: F401
27 cast, Any, Callable, Dict, Generator, Iterable, List, Literal, Mapping, NewType,
28 Optional, Set, Tuple, Type, TypeVar, Union,
29)
30from unittest.mock import Mock
31from warnings import warn
33from mako.lookup import TemplateLookup
34from mako.exceptions import TopLevelLookupException
35from mako.template import Template
37try:
38 from pdoc._version import version as __version__ # noqa: F401
39except ImportError:
40 __version__ = '???' # Package not installed
43_get_type_hints = lru_cache()(typing.get_type_hints)
45_URL_MODULE_SUFFIX = '.html'
46_URL_INDEX_MODULE_SUFFIX = '.m.html' # For modules named literal 'index'
47_URL_PACKAGE_SUFFIX = '/index.html'
49# type.__module__ can be None by the Python spec. In those cases, use this value
50_UNKNOWN_MODULE = '?'
52T = TypeVar('T', 'Module', 'Class', 'Function', 'Variable')
54__pdoc__: Dict[str, Union[bool, str]] = {}
56tpl_lookup = TemplateLookup(
57 cache_args=dict(cached=True,
58 cache_type='memory'),
59 input_encoding='utf-8',
60 directories=[path.join(path.dirname(__file__), "templates")],
61)
62"""
63A `mako.lookup.TemplateLookup` object that knows how to load templates
64from the file system. You may add additional paths by modifying the
65object's `directories` attribute.
66"""
67if os.getenv("XDG_CONFIG_HOME"):
68 tpl_lookup.directories.insert(0, path.join(os.getenv("XDG_CONFIG_HOME", ''), "pdoc"))
71class Context(dict):
72 """
73 The context object that maps all documented identifiers
74 (`pdoc.Doc.refname`) to their respective `pdoc.Doc` objects.
76 You can pass an instance of `pdoc.Context` to `pdoc.Module` constructor.
77 All `pdoc.Module` objects that share the same `pdoc.Context` will see
78 (and be able to link in HTML to) each other's identifiers.
80 If you don't pass your own `Context` instance to `Module` constructor,
81 a global context object will be used.
82 """
83 __pdoc__['Context.__init__'] = False
85 def __init__(self, *args, **kwargs):
86 super().__init__(*args, **kwargs)
87 # A surrogate so that the check in Module._link_inheritance()
88 # "__pdoc__-overriden key {!r} does not exist" can see the object
89 # (and not warn).
90 self.blacklisted = getattr(args[0], 'blacklisted', set()) if args else set()
93_global_context = Context()
96def reset():
97 """Resets the global `pdoc.Context` to the initial (empty) state."""
98 global _global_context
99 _global_context.clear()
101 # Clear LRU caches
102 for func in (_get_type_hints,
103 _is_blacklisted,
104 _is_whitelisted):
105 func.cache_clear()
106 for cls in (Doc, Module, Class, Function, Variable, External):
107 for _, method in inspect.getmembers(cls):
108 if isinstance(method, property):
109 method = method.fget
110 if hasattr(method, 'cache_clear'):
111 method.cache_clear()
114def _get_config(**kwargs):
115 # Apply config.mako configuration
116 MAKO_INTERNALS = Template('').module.__dict__.keys()
117 DEFAULT_CONFIG = path.join(path.dirname(__file__), 'templates', 'config.mako')
118 config = {}
119 for config_module in (Template(filename=DEFAULT_CONFIG).module,
120 tpl_lookup.get_template('/config.mako').module):
121 config.update((var, getattr(config_module, var, None))
122 for var in config_module.__dict__
123 if var not in MAKO_INTERNALS)
125 known_keys = (set(config)
126 | {'docformat'} # Feature. https://github.com/pdoc3/pdoc/issues/169
127 # deprecated
128 | {'module', 'modules', 'http_server', 'external_links', 'search_query'})
129 invalid_keys = {k: v for k, v in kwargs.items() if k not in known_keys}
130 if invalid_keys:
131 warn(f'Unknown configuration variables (not in config.mako): {invalid_keys}')
132 config.update(kwargs)
134 if 'search_query' in config:
135 warn('Option `search_query` has been deprecated. Use `google_search_query` instead',
136 DeprecationWarning, stacklevel=2)
137 config['google_search_query'] = config['search_query']
138 del config['search_query']
140 return config
143def _render_template(template_name, **kwargs):
144 """
145 Returns the Mako template with the given name. If the template
146 cannot be found, a nicer error message is displayed.
147 """
148 config = _get_config(**kwargs)
150 try:
151 t = tpl_lookup.get_template(template_name)
152 except TopLevelLookupException:
153 paths = [path.join(p, template_name.lstrip('/')) for p in tpl_lookup.directories]
154 raise OSError(f"No template found at any of: {', '.join(paths)}")
156 try:
157 return t.render(**config).strip()
158 except Exception:
159 from mako import exceptions
160 print(exceptions.text_error_template().render(),
161 file=sys.stderr)
162 raise
165def html(module_name, docfilter=None, reload=False, skip_errors=False, **kwargs) -> str:
166 """
167 Returns the documentation for the module `module_name` in HTML
168 format. The module must be a module or an importable string.
170 `docfilter` is an optional predicate that controls which
171 documentation objects are shown in the output. It is a function
172 that takes a single argument (a documentation object) and returns
173 `True` or `False`. If `False`, that object will not be documented.
174 """
175 mod = Module(import_module(module_name, reload=reload, skip_errors=False),
176 docfilter=docfilter, skip_errors=skip_errors)
177 link_inheritance()
178 return mod.html(**kwargs)
181def text(module_name, docfilter=None, reload=False, skip_errors=False, **kwargs) -> str:
182 """
183 Returns the documentation for the module `module_name` in plain
184 text format suitable for viewing on a terminal.
185 The module must be a module or an importable string.
187 `docfilter` is an optional predicate that controls which
188 documentation objects are shown in the output. It is a function
189 that takes a single argument (a documentation object) and returns
190 `True` or `False`. If `False`, that object will not be documented.
191 """
192 mod = Module(import_module(module_name, reload=reload, skip_errors=False),
193 docfilter=docfilter, skip_errors=skip_errors)
194 link_inheritance()
195 return mod.text(**kwargs)
198def import_module(
199 module: Union[str, ModuleType],
200 *,
201 reload: bool = False,
202 skip_errors: bool = False,
203) -> ModuleType:
204 """
205 Return module object matching `module` specification (either a python
206 module path or a filesystem path to file/directory).
207 """
208 @contextmanager
209 def _module_path(module):
210 from os.path import abspath, dirname, isfile, isdir, split
211 path = '_pdoc_dummy_nonexistent'
212 module_name = inspect.getmodulename(module)
213 if isdir(module):
214 path, module = split(abspath(module))
215 elif isfile(module) and module_name:
216 path, module = dirname(abspath(module)), module_name
217 try:
218 sys.path.insert(0, path)
219 yield module
220 finally:
221 sys.path.remove(path)
223 if isinstance(module, Module):
224 module = module.obj
225 if isinstance(module, str):
226 with _module_path(module) as module_path:
227 try:
228 module = importlib.import_module(module_path)
229 except Exception as e:
230 msg = f'Error importing {module!r}: {e.__class__.__name__}: {e}'
231 if not skip_errors:
232 raise ImportError(msg)
233 warn(msg, category=Module.ImportWarning, stacklevel=2)
234 module = ModuleType(module_path)
236 assert inspect.ismodule(module)
237 # If this is pdoc itself, return without reloading. Otherwise later
238 # `isinstance(..., pdoc.Doc)` calls won't work correctly.
239 if reload and not module.__name__.startswith(__name__):
240 module = importlib.reload(module)
241 # We recursively reload all submodules, in case __all_ is used - cf. issue #264
242 for mod_key, mod in list(sys.modules.items()):
243 if mod_key.startswith(module.__name__):
244 importlib.reload(mod)
245 return module
248def _pairwise(iterable):
249 """s -> (s0,s1), (s1,s2), (s2, s3), ..."""
250 a, b = tee(iterable)
251 next(b, None)
252 return zip(a, b)
255def _pep224_docstrings(doc_obj: Union['Module', 'Class'], *,
256 _init_tree=None) -> Tuple[Dict[str, str],
257 Dict[str, str]]:
258 """
259 Extracts PEP-224 docstrings and doc-comments (`#: ...`) for variables of `doc_obj`
260 (either a `pdoc.Module` or `pdoc.Class`).
262 Returns a tuple of two dicts mapping variable names to their docstrings.
263 The second dict contains instance variables and is non-empty only in case
264 `doc_obj` is a `pdoc.Class` which has `__init__` method.
265 """
266 # No variables in namespace packages
267 if isinstance(doc_obj, Module) and doc_obj.is_namespace:
268 return {}, {}
270 vars: Dict[str, str] = {}
271 instance_vars: Dict[str, str] = {}
273 if _init_tree:
274 tree = _init_tree
275 else:
276 try:
277 # Maybe raise exceptions with appropriate message
278 # before using cleaned doc_obj.source
279 _ = inspect.findsource(doc_obj.obj)
280 tree = ast.parse(doc_obj.source)
281 except (OSError, TypeError, SyntaxError, UnicodeDecodeError) as exc:
282 # Don't emit a warning for builtins that don't have source available
283 is_builtin = getattr(doc_obj.obj, '__module__', None) == 'builtins'
284 if not is_builtin:
285 warn(f"Couldn't read PEP-224 variable docstrings from {doc_obj!r}: {exc}",
286 stacklevel=3 + int(isinstance(doc_obj, Class)))
287 return {}, {}
289 if isinstance(doc_obj, Class):
290 tree = tree.body[0] # ast.parse creates a dummy ast.Module wrapper
292 # For classes, maybe add instance variables defined in __init__
293 # Get the *last* __init__ node in case it is preceded by @overloads.
294 for node in reversed(tree.body): # type: ignore
295 if isinstance(node, ast.FunctionDef) and node.name == '__init__':
296 instance_vars, _ = _pep224_docstrings(doc_obj, _init_tree=node)
297 break
299 def get_name(assign_node):
300 if isinstance(assign_node, ast.Assign) and len(assign_node.targets) == 1:
301 target = assign_node.targets[0]
302 elif isinstance(assign_node, ast.AnnAssign):
303 target = assign_node.target
304 # Skip the annotation. PEP 526 says:
305 # > Putting the instance variable annotations together in the class
306 # > makes it easier to find them, and helps a first-time reader of the code.
307 else:
308 return None
310 if not _init_tree and isinstance(target, ast.Name):
311 name = target.id
312 elif (_init_tree and
313 isinstance(target, ast.Attribute) and
314 isinstance(target.value, ast.Name) and
315 target.value.id == 'self'):
316 name = target.attr
317 else:
318 return None
320 if not _is_public(name) and not _is_whitelisted(name, doc_obj):
321 return None
323 return name
325 # For handling PEP-224 docstrings for variables
326 for assign_node, str_node in _pairwise(ast.iter_child_nodes(tree)):
327 if not (isinstance(assign_node, (ast.Assign, ast.AnnAssign)) and
328 isinstance(str_node, ast.Expr) and
329 isinstance(str_node.value, ast.Constant)):
330 continue
332 name = get_name(assign_node)
333 if not name:
334 continue
336 docstring = inspect.cleandoc(str_node.value.value).strip()
337 if not docstring:
338 continue
340 vars[name] = docstring
342 # For handling '#:' docstrings for variables
343 for assign_node in ast.iter_child_nodes(tree):
344 if not isinstance(assign_node, (ast.Assign, ast.AnnAssign)):
345 continue
347 name = get_name(assign_node)
348 if not name:
349 continue
351 # Already documented. PEP-224 method above takes precedence.
352 if name in vars:
353 continue
355 def get_indent(line):
356 return len(line) - len(line.lstrip())
358 source_lines = doc_obj.source.splitlines()
359 assign_line = source_lines[assign_node.lineno - 1]
360 assign_indent = get_indent(assign_line)
361 comment_lines = []
362 MARKER = '#: '
363 for line in reversed(source_lines[:assign_node.lineno - 1]):
364 if get_indent(line) == assign_indent and line.lstrip().startswith(MARKER):
365 comment_lines.append(line.split(MARKER, maxsplit=1)[1])
366 else:
367 break
369 # Since we went 'up' need to reverse lines to be in correct order
370 comment_lines = comment_lines[::-1]
372 # Finally: check for a '#: ' comment at the end of the assignment line itself.
373 if MARKER in assign_line:
374 comment_lines.append(assign_line.rsplit(MARKER, maxsplit=1)[1])
376 if comment_lines:
377 vars[name] = '\n'.join(comment_lines)
379 return vars, instance_vars
382@lru_cache()
383def _is_whitelisted(name: str, doc_obj: Union['Module', 'Class']):
384 """
385 Returns `True` if `name` (relative or absolute refname) is
386 contained in some module's __pdoc__ with a truish value.
387 """
388 refname = f'{doc_obj.refname}.{name}'
389 module: Optional[Module] = doc_obj.module
390 while module:
391 qualname = refname[len(module.refname) + 1:]
392 if module.__pdoc__.get(qualname) or module.__pdoc__.get(refname):
393 return True
394 module = module.supermodule
395 return False
398@lru_cache()
399def _is_blacklisted(name: str, doc_obj: Union['Module', 'Class']):
400 """
401 Returns `True` if `name` (relative or absolute refname) is
402 contained in some module's __pdoc__ with value False.
403 """
404 refname = f'{doc_obj.refname}.{name}'
405 module: Optional[Module] = doc_obj.module
406 while module:
407 qualname = refname[len(module.refname) + 1:]
408 if module.__pdoc__.get(qualname) is False or module.__pdoc__.get(refname) is False:
409 return True
410 module = module.supermodule
411 return False
414def _is_public(ident_name):
415 """
416 Returns `True` if `ident_name` matches the export criteria for an
417 identifier name.
418 """
419 return not ident_name.startswith("_")
422def _is_function(obj):
423 return inspect.isroutine(obj) and callable(obj) and not isinstance(obj, Mock) # Mock: GH-350
426def _is_descriptor(obj):
427 return (inspect.isdatadescriptor(obj) or
428 inspect.ismethoddescriptor(obj) or
429 inspect.isgetsetdescriptor(obj) or
430 inspect.ismemberdescriptor(obj))
433def _unwrap_descriptor(dobj):
434 obj = dobj.obj
435 if isinstance(obj, property):
436 return (getattr(obj, 'fget', False) or
437 getattr(obj, 'fset', False) or
438 getattr(obj, 'fdel', obj))
439 if isinstance(obj, cached_property):
440 return obj.func
441 if isinstance(obj, FunctionType):
442 return obj
443 if (inspect.ismemberdescriptor(obj) or
444 getattr(getattr(obj, '__class__', 0), '__name__', 0) == '_tuplegetter'):
445 class_name = dobj.qualname.rsplit('.', 1)[0]
446 obj = getattr(dobj.module.obj, class_name)
447 return obj
448 # XXX: Follow descriptor protocol? Already proved buggy in conditions above
449 return getattr(obj, '__get__', obj)
452def _filter_type(type: Type[T],
453 values: Union[Iterable['Doc'], Mapping[str, 'Doc']]) -> List[T]:
454 """
455 Return a list of values from `values` of type `type`.
456 """
457 if isinstance(values, dict):
458 values = values.values()
459 return [i for i in values if isinstance(i, type)]
462def _toposort(graph: Mapping[T, Set[T]]) -> Generator[T, None, None]:
463 """
464 Return items of `graph` sorted in topological order.
465 Source: https://rosettacode.org/wiki/Topological_sort#Python
466 """
467 items_without_deps = reduce(set.union, graph.values(), set()) - set(graph.keys()) # type: ignore # noqa: E501
468 yield from items_without_deps
469 ordered = items_without_deps
470 while True:
471 graph = {item: (deps - ordered)
472 for item, deps in graph.items()
473 if item not in ordered}
474 ordered = {item
475 for item, deps in graph.items()
476 if not deps}
477 yield from ordered
478 if not ordered:
479 break
480 assert not graph, f"A cyclic dependency exists amongst {graph!r}"
483def link_inheritance(context: Optional[Context] = None):
484 """
485 Link inheritance relationsships between `pdoc.Class` objects
486 (and between their members) of all `pdoc.Module` objects that
487 share the provided `context` (`pdoc.Context`).
489 You need to call this if you expect `pdoc.Doc.inherits` and
490 inherited `pdoc.Doc.docstring` to be set correctly.
491 """
492 if context is None:
493 context = _global_context
495 graph = {cls: set(cls.mro(only_documented=True))
496 for cls in _filter_type(Class, context)}
498 for cls in _toposort(graph):
499 cls._fill_inheritance()
501 for module in _filter_type(Module, context):
502 module._link_inheritance()
505class Doc:
506 """
507 A base class for all documentation objects.
509 A documentation object corresponds to *something* in a Python module
510 that has a docstring associated with it. Typically, this includes
511 modules, classes, functions, and methods. However, `pdoc` adds support
512 for extracting some docstrings from abstract syntax trees, making
513 (module, class or instance) variables supported too.
515 A special type of documentation object `pdoc.External` is used to
516 represent identifiers that are not part of the public interface of
517 a module. (The name "External" is a bit of a misnomer, since it can
518 also correspond to unexported members of the module, particularly in
519 a class's ancestor list.)
520 """
522 def __init__(self, name: str, module, obj, docstring: str = ''):
523 """
524 Initializes a documentation object, where `name` is the public
525 identifier name, `module` is a `pdoc.Module` object where raw
526 Python object `obj` is defined, and `docstring` is its
527 documentation string. If `docstring` is left empty, it will be
528 read with `inspect.getdoc()`.
529 """
530 self.module = module
531 """
532 The module documentation object that this object is defined in.
533 """
535 self.name = name
536 """
537 The identifier name for this object.
538 """
540 self.obj = obj
541 """
542 The raw python object.
543 """
545 docstring = (docstring or inspect.getdoc(obj) or '').strip()
546 if '.. include::' in docstring:
547 from pdoc.html_helpers import _ToMarkdown
548 docstring = _ToMarkdown.admonitions(docstring, self.module, ('include',))
549 self.docstring = docstring
550 """
551 The cleaned docstring for this object with any `.. include::`
552 directives resolved (i.e. content included).
553 """
555 self.inherits: Optional[Union[Class, Function, Variable]] = None
556 """
557 The Doc object (Class, Function, or Variable) this object inherits from,
558 if any.
559 """
561 def __repr__(self):
562 return f'<{self.__class__.__name__} {self.refname!r}>'
564 @property
565 @lru_cache()
566 def source(self) -> str:
567 """
568 Cleaned (dedented) source code of the Python object. If not
569 available, an empty string.
570 """
571 try:
572 lines, _ = inspect.getsourcelines(_unwrap_descriptor(self))
573 except (ValueError, TypeError, OSError):
574 return ''
575 return inspect.cleandoc(''.join(['\n'] + lines))
577 @property
578 def refname(self) -> str:
579 """
580 Reference name of this documentation
581 object, usually its fully qualified path
582 (e.g. <code>pdoc.Doc.refname</code>). Every
583 documentation object provides this property.
584 """
585 # Ok for Module and External, the rest need it overriden
586 return self.name
588 @property
589 def qualname(self) -> str:
590 """
591 Module-relative "qualified" name of this documentation
592 object, used for show (e.g. <code>Doc.qualname</code>).
593 """
594 return getattr(self.obj, '__qualname__', self.name)
596 @lru_cache()
597 def url(self, relative_to: Optional['Module'] = None, *, link_prefix: str = '',
598 top_ancestor: bool = False) -> str:
599 """
600 Canonical relative URL (including page fragment) for this
601 documentation object.
603 Specify `relative_to` (a `pdoc.Module` object) to obtain a
604 relative URL.
606 For usage of `link_prefix` see `pdoc.html()`.
608 If `top_ancestor` is `True`, the returned URL instead points to
609 the top ancestor in the object's `pdoc.Doc.inherits` chain.
610 """
611 if top_ancestor:
612 self = self._inherits_top()
614 if relative_to is None or link_prefix:
615 return link_prefix + self._url()
617 if self.module.name == relative_to.name:
618 return f'#{self.refname}'
620 # Otherwise, compute relative path from current module to link target
621 url = os.path.relpath(self._url(), relative_to.url()).replace(path.sep, '/')
622 # We have one set of '..' too many
623 if url.startswith('../'):
624 url = url[3:]
625 return url
627 def _url(self):
628 return f'{self.module._url()}#{self.refname}'
630 def _inherits_top(self):
631 """
632 Follow the `pdoc.Doc.inherits` chain and return the top object.
633 """
634 top = self
635 while top.inherits:
636 top = top.inherits
637 return top
639 def __lt__(self, other):
640 return self.refname < other.refname
643class Module(Doc):
644 """
645 Representation of a module's documentation.
646 """
647 __pdoc__["Module.name"] = """
648 The name of this module with respect to the context/path in which
649 it was imported from. It is always an absolute import path.
650 """
652 def __init__(self, module: Union[ModuleType, str], *,
653 docfilter: Optional[Callable[[Doc], bool]] = None,
654 supermodule: Optional['Module'] = None,
655 context: Optional[Context] = None,
656 skip_errors: bool = False):
657 """
658 Creates a `Module` documentation object given the actual
659 module Python object.
661 `docfilter` is an optional predicate that controls which
662 sub-objects are documentated (see also: `pdoc.html()`).
664 `supermodule` is the parent `pdoc.Module` this module is
665 a submodule of.
667 `context` is an instance of `pdoc.Context`. If `None` a
668 global context object will be used.
670 If `skip_errors` is `True` and an unimportable, erroneous
671 submodule is encountered, a warning will be issued instead
672 of raising an exception.
673 """
674 if isinstance(module, str):
675 module = import_module(module, skip_errors=skip_errors)
677 super().__init__(module.__name__, self, module)
678 if self.name.endswith('.__init__') and not self.is_package:
679 self.name = self.name[:-len('.__init__')]
681 self._context = _global_context if context is None else context
682 """
683 A lookup table for ALL doc objects of all modules that share this context,
684 mainly used in `Module.find_ident()`.
685 """
686 assert isinstance(self._context, Context), \
687 'pdoc.Module(context=) should be a pdoc.Context instance'
689 self.supermodule = supermodule
690 """
691 The parent `pdoc.Module` this module is a submodule of, or `None`.
692 """
694 self.doc: Dict[str, Union[Module, Class, Function, Variable]] = {}
695 """A mapping from identifier name to a documentation object."""
697 self._is_inheritance_linked = False
698 """Re-entry guard for `pdoc.Module._link_inheritance()`."""
700 self._skipped_submodules = set()
702 var_docstrings, _ = _pep224_docstrings(self)
704 # Populate self.doc with this module's public members
705 public_objs = []
706 if hasattr(self.obj, '__all__'):
707 for name in self.obj.__all__:
708 try:
709 obj = getattr(self.obj, name)
710 except AttributeError:
711 warn(f"Module {self.module!r} doesn't contain identifier `{name}` "
712 "exported in `__all__`")
713 else:
714 if not _is_blacklisted(name, self):
715 obj = inspect.unwrap(obj)
716 public_objs.append((name, obj))
717 else:
718 def is_from_this_module(obj):
719 mod = inspect.getmodule(inspect.unwrap(obj))
720 return mod is None or mod.__name__ == self.obj.__name__
722 for name, obj in inspect.getmembers(self.obj):
723 if ((_is_public(name) or
724 _is_whitelisted(name, self)) and
725 (_is_blacklisted(name, self) or # skips unwrapping that follows
726 is_from_this_module(obj) or
727 name in var_docstrings)):
729 if _is_blacklisted(name, self):
730 self._context.blacklisted.add(f'{self.refname}.{name}')
731 continue
733 obj = inspect.unwrap(obj)
734 public_objs.append((name, obj))
736 index = list(self.obj.__dict__).index
737 public_objs.sort(key=lambda i: index(i[0]))
739 for name, obj in public_objs:
740 if _is_function(obj):
741 self.doc[name] = Function(name, self, obj)
742 elif inspect.isclass(obj):
743 self.doc[name] = Class(name, self, obj)
744 elif name in var_docstrings:
745 self.doc[name] = Variable(name, self, var_docstrings[name], obj=obj)
747 # If the module is a package, scan the directory for submodules
748 if self.is_package:
750 def iter_modules(paths):
751 """
752 Custom implementation of `pkgutil.iter_modules()`
753 because that one doesn't play well with namespace packages.
754 See: https://github.com/pypa/setuptools/issues/83
755 """
756 from os.path import isdir, join
757 for pth in paths:
758 if pth.startswith("__editable__."):
759 # See https://github.com/pypa/pip/issues/11380
760 continue
761 for file in os.listdir(pth):
762 if file.startswith(('.', '__pycache__', '__init__.py')):
763 continue
764 module_name = inspect.getmodulename(file)
765 if module_name:
766 yield module_name
767 if isdir(join(pth, file)) and '.' not in file:
768 yield file
770 for root in iter_modules(self.obj.__path__):
771 # Ignore if this module was already doc'd.
772 if root in self.doc:
773 continue
775 # Ignore if it isn't exported
776 if not _is_public(root) and not _is_whitelisted(root, self):
777 continue
778 if _is_blacklisted(root, self):
779 self._skipped_submodules.add(root)
780 continue
782 assert self.refname == self.name
783 fullname = f"{self.name}.{root}"
784 m = Module(import_module(fullname, skip_errors=skip_errors),
785 docfilter=docfilter, supermodule=self,
786 context=self._context, skip_errors=skip_errors)
788 self.doc[root] = m
789 # Skip empty namespace packages because they may
790 # as well be other auxiliary directories
791 if m.is_namespace and not m.doc:
792 del self.doc[root]
793 self._context.pop(m.refname)
795 # Apply docfilter
796 if docfilter:
797 for name, dobj in self.doc.copy().items():
798 if not docfilter(dobj):
799 self.doc.pop(name)
800 self._context.pop(dobj.refname, None)
802 # Build the reference name dictionary of the module
803 self._context[self.refname] = self
804 for docobj in self.doc.values():
805 self._context[docobj.refname] = docobj
806 if isinstance(docobj, Class):
807 self._context.update((obj.refname, obj)
808 for obj in docobj.doc.values())
810 class ImportWarning(UserWarning):
811 """
812 Our custom import warning because the builtin is ignored by default.
813 https://docs.python.org/3/library/warnings.html#default-warning-filter
814 """
816 __pdoc__['Module.ImportWarning'] = False
818 @property
819 def __pdoc__(self) -> dict:
820 """This module's __pdoc__ dict, or an empty dict if none."""
821 return getattr(self.obj, '__pdoc__', {})
823 def _link_inheritance(self):
824 # Inherited members are already in place since
825 # `Class._fill_inheritance()` has been called from
826 # `pdoc.fill_inheritance()`.
827 # Now look for docstrings in the module's __pdoc__ override.
829 if self._is_inheritance_linked:
830 # Prevent re-linking inheritance for modules which have already
831 # had done so. Otherwise, this would raise "does not exist"
832 # errors if `pdoc.link_inheritance()` is called multiple times.
833 return
835 # Apply __pdoc__ overrides
836 for name, docstring in self.__pdoc__.items():
837 # In case of whitelisting with "True", there's nothing to do
838 if docstring is True:
839 continue
841 refname = f"{self.refname}.{name}"
842 if docstring in (False, None):
843 if docstring is None:
844 warn('Setting `__pdoc__[key] = None` is deprecated; '
845 'use `__pdoc__[key] = False` '
846 f'(key: {name!r}, module: {self.name!r}).')
848 if name in self._skipped_submodules:
849 continue
851 if (not name.endswith('.__init__') and
852 name not in self.doc and
853 refname not in self._context and
854 refname not in self._context.blacklisted):
855 warn(f'__pdoc__-overriden key {name!r} does not exist '
856 f'in module {self.name!r}')
858 obj = self.find_ident(name)
859 cls = getattr(obj, 'cls', None)
860 if cls:
861 del cls.doc[obj.name]
862 self.doc.pop(name, None)
863 self._context.pop(refname, None)
865 # Pop also all that startwith refname
866 for key in list(self._context.keys()):
867 if key.startswith(refname + '.'):
868 del self._context[key]
870 continue
872 dobj = self.find_ident(refname)
873 if isinstance(dobj, External):
874 continue
875 if not isinstance(docstring, str):
876 raise ValueError('__pdoc__ dict values must be strings; '
877 f'__pdoc__[{name!r}] is of type {type(docstring)}')
878 dobj.docstring = inspect.cleandoc(docstring)
880 # Now after docstrings are set correctly, continue the
881 # inheritance routine, marking members inherited or not
882 for c in _filter_type(Class, self.doc):
883 c._link_inheritance()
885 self._is_inheritance_linked = True
887 def text(self, **kwargs) -> str:
888 """
889 Returns the documentation for this module as plain text.
890 """
891 txt = _render_template('/text.mako', module=self, **kwargs)
892 txt = re.sub("\n\n\n+", "\n\n", txt)
893 if not txt.endswith('\n'):
894 txt += '\n'
895 return txt
897 def html(self, minify=True, **kwargs) -> str:
898 """
899 Returns the documentation for this module as
900 self-contained HTML.
902 If `minify` is `True`, the resulting HTML is minified.
904 For explanation of other arguments, see `pdoc.html()`.
906 `kwargs` is passed to the `mako` render function.
907 """
908 html = _render_template('/html.mako', module=self, **kwargs)
909 if minify:
910 from pdoc.html_helpers import minify_html
911 html = minify_html(html)
912 if not html.endswith('\n'):
913 html = html + '\n'
914 return html
916 @property
917 def is_package(self) -> bool:
918 """
919 `True` if this module is a package.
921 Works by checking whether the module has a `__path__` attribute.
922 """
923 return hasattr(self.obj, "__path__")
925 @property
926 def is_namespace(self) -> bool:
927 """
928 `True` if this module is a namespace package.
929 """
930 try:
931 return self.obj.__spec__.origin in (None, 'namespace') # None in Py3.7+
932 except AttributeError:
933 return False
935 def find_class(self, cls: type) -> Doc:
936 """
937 Given a Python `cls` object, try to find it in this module
938 or in any of the exported identifiers of the submodules.
939 """
940 # XXX: Is this corrent? Does it always match
941 # `Class.module.name + Class.qualname`?. Especially now?
942 # If not, see what was here before.
943 return self.find_ident(f'{cls.__module__ or _UNKNOWN_MODULE}.{cls.__qualname__}')
945 def find_ident(self, name: str) -> Doc:
946 """
947 Searches this module and **all** other public modules
948 for an identifier with name `name` in its list of
949 exported identifiers.
951 The documentation object corresponding to the identifier is
952 returned. If one cannot be found, then an instance of
953 `External` is returned populated with the given identifier.
954 """
955 _name = name.rstrip('()') # Function specified with parentheses
957 if _name.endswith('.__init__'): # Ref to class' init is ref to class itself
958 _name = _name[:-len('.__init__')]
960 return (self.doc.get(_name) or
961 self._context.get(_name) or
962 self._context.get(f'{self.name}.{_name}') or
963 External(name))
965 def _filter_doc_objs(self, type: Type[T], sort=True) -> List[T]:
966 result = _filter_type(type, self.doc)
967 return sorted(result) if sort else result
969 def variables(self, sort=True) -> List['Variable']:
970 """
971 Returns all documented module-level variables in the module,
972 optionally sorted alphabetically, as a list of `pdoc.Variable`.
973 """
974 return self._filter_doc_objs(Variable, sort)
976 def classes(self, sort=True) -> List['Class']:
977 """
978 Returns all documented module-level classes in the module,
979 optionally sorted alphabetically, as a list of `pdoc.Class`.
980 """
981 return self._filter_doc_objs(Class, sort)
983 def functions(self, sort=True) -> List['Function']:
984 """
985 Returns all documented module-level functions in the module,
986 optionally sorted alphabetically, as a list of `pdoc.Function`.
987 """
988 return self._filter_doc_objs(Function, sort)
990 def submodules(self) -> List['Module']:
991 """
992 Returns all documented sub-modules of the module sorted
993 alphabetically as a list of `pdoc.Module`.
994 """
995 return self._filter_doc_objs(Module)
997 def _url(self):
998 url = self.module.name.replace('.', '/')
999 if self.is_package:
1000 return url + _URL_PACKAGE_SUFFIX
1001 elif url.endswith('/index'):
1002 return url + _URL_INDEX_MODULE_SUFFIX
1003 return url + _URL_MODULE_SUFFIX
1006def _getmembers_all(obj: type) -> List[Tuple[str, Any]]:
1007 # The following code based on inspect.getmembers() @ 5b23f7618d43
1008 mro = obj.__mro__[:-1] # Skip object
1009 names = set(dir(obj))
1010 # Add keys from bases
1011 for base in mro:
1012 names.update(base.__dict__.keys())
1013 # Add members for which type annotations exist
1014 names.update(getattr(obj, '__annotations__', {}).keys())
1016 results = []
1017 for name in names:
1018 try:
1019 value = getattr(obj, name)
1020 except AttributeError:
1021 for base in mro:
1022 if name in base.__dict__:
1023 value = base.__dict__[name]
1024 break
1025 else:
1026 # Missing slot member or a buggy __dir__;
1027 # In out case likely a type-annotated member
1028 # which we'll interpret as a variable
1029 value = None
1030 results.append((name, value))
1031 return results
1034class Class(Doc):
1035 """
1036 Representation of a class' documentation.
1037 """
1038 def __init__(self, name: str, module: Module, obj, *, docstring: Optional[str] = None):
1039 assert inspect.isclass(obj)
1041 if docstring is None:
1042 init_doc = inspect.getdoc(obj.__init__) or ''
1043 if init_doc == object.__init__.__doc__:
1044 init_doc = ''
1045 docstring = f'{inspect.getdoc(obj) or ""}\n\n{init_doc}'.strip()
1047 super().__init__(name, module, obj, docstring=docstring)
1049 self.doc: Dict[str, Union[Function, Variable]] = {}
1050 """A mapping from identifier name to a `pdoc.Doc` objects."""
1052 # Annotations for filtering.
1053 # Use only own, non-inherited annotations (the rest will be inherited)
1054 annotations = getattr(self.obj, '__annotations__', {})
1056 public_objs = []
1057 for _name, obj in _getmembers_all(self.obj):
1058 # Filter only *own* members. The rest are inherited
1059 # in Class._fill_inheritance()
1060 if ((_name in self.obj.__dict__ or
1061 _name in annotations) and
1062 (_is_public(_name) or
1063 _is_whitelisted(_name, self))):
1065 if _is_blacklisted(_name, self):
1066 self.module._context.blacklisted.add(f'{self.refname}.{_name}')
1067 continue
1069 obj = inspect.unwrap(obj)
1070 public_objs.append((_name, obj))
1072 def definition_order_index(
1073 name,
1074 _annot_index=list(annotations).index,
1075 _dict_index=list(self.obj.__dict__).index):
1076 try:
1077 return _dict_index(name)
1078 except ValueError:
1079 pass
1080 try:
1081 return _annot_index(name) - len(annotations) # sort annotated first
1082 except ValueError:
1083 return 9e9
1085 public_objs.sort(key=lambda i: definition_order_index(i[0]))
1087 var_docstrings, instance_var_docstrings = _pep224_docstrings(self)
1089 # Convert the public Python objects to documentation objects.
1090 for name, obj in public_objs:
1091 if _is_function(obj):
1092 self.doc[name] = Function(
1093 name, self.module, obj, cls=self)
1094 else:
1095 self.doc[name] = Variable(
1096 name, self.module,
1097 docstring=(
1098 var_docstrings.get(name) or
1099 (inspect.isclass(obj) or _is_descriptor(obj)) and inspect.getdoc(obj)),
1100 cls=self,
1101 kind="prop" if isinstance(obj, property) else "var",
1102 obj=_is_descriptor(obj) and obj or None,
1103 instance_var=(_is_descriptor(obj) or
1104 name in getattr(self.obj, '__slots__', ()) or
1105 (is_dataclass(self.obj) and name in annotations)))
1107 for name, docstring in instance_var_docstrings.items():
1108 self.doc[name] = Variable(
1109 name, self.module, docstring, cls=self,
1110 obj=getattr(self.obj, name, None),
1111 instance_var=True)
1113 @staticmethod
1114 def _method_type(cls: type, name: str):
1115 """
1116 Returns `None` if the method `name` of class `cls`
1117 is a regular method. Otherwise, it returns
1118 `classmethod` or `staticmethod`, as appropriate.
1119 """
1120 func = getattr(cls, name, None)
1121 if inspect.ismethod(func):
1122 # If the function is already bound, it's a classmethod.
1123 # Regular methods are not bound before initialization.
1124 return classmethod
1125 for c in inspect.getmro(cls):
1126 if name in c.__dict__:
1127 if isinstance(c.__dict__[name], staticmethod):
1128 return staticmethod
1129 return None
1130 raise RuntimeError(f"{cls}.{name} not found")
1132 @property
1133 def refname(self) -> str:
1134 return f'{self.module.name}.{self.qualname}'
1136 def mro(self, only_documented=False) -> List['Class']:
1137 """
1138 Returns a list of ancestor (superclass) documentation objects
1139 in method resolution order.
1141 The list will contain objects of type `pdoc.Class`
1142 if the types are documented, and `pdoc.External` otherwise.
1143 """
1144 classes = [cast(Class, self.module.find_class(c))
1145 for c in inspect.getmro(self.obj)
1146 if c not in (self.obj, object)]
1147 if self in classes:
1148 # This can contain self in case of a class inheriting from
1149 # a class with (previously) the same name. E.g.
1150 #
1151 # class Loc(namedtuple('Loc', 'lat lon')): ...
1152 #
1153 # We remove it from ancestors so that toposort doesn't break.
1154 classes.remove(self)
1155 if only_documented:
1156 classes = _filter_type(Class, classes)
1157 return classes
1159 def subclasses(self) -> List['Class']:
1160 """
1161 Returns a list of subclasses of this class that are visible to the
1162 Python interpreter (obtained from `type.__subclasses__()`).
1164 The objects in the list are of type `pdoc.Class` if available,
1165 and `pdoc.External` otherwise.
1166 """
1167 return sorted(cast(Class, self.module.find_class(c))
1168 for c in type.__subclasses__(self.obj))
1170 def params(self, *, annotate=False, link=None) -> List[str]:
1171 """
1172 Return a list of formatted parameters accepted by the
1173 class constructor (method `__init__`). See `pdoc.Function.params`.
1174 """
1175 name = self.name + '.__init__'
1176 qualname = self.qualname + '.__init__'
1177 refname = self.refname + '.__init__'
1178 exclusions = self.module.__pdoc__
1179 if name in exclusions or qualname in exclusions or refname in exclusions:
1180 return []
1182 return Function._params(self, annotate=annotate, link=link, module=self.module)
1184 def _filter_doc_objs(self, type: Type[T], include_inherited=True,
1185 filter_func: Callable[[T], bool] = lambda x: True,
1186 sort=True) -> List[T]:
1187 result = [obj for obj in _filter_type(type, self.doc)
1188 if (include_inherited or not obj.inherits) and filter_func(obj)]
1189 return sorted(result) if sort else result
1191 def class_variables(self, include_inherited=True, sort=True) -> List['Variable']:
1192 """
1193 Returns an optionally-sorted list of `pdoc.Variable` objects that
1194 represent this class' class variables.
1195 """
1196 return self._filter_doc_objs(
1197 Variable, include_inherited, lambda dobj: not dobj.instance_var,
1198 sort)
1200 def instance_variables(self, include_inherited=True, sort=True) -> List['Variable']:
1201 """
1202 Returns an optionally-sorted list of `pdoc.Variable` objects that
1203 represent this class' instance variables. Instance variables
1204 are those defined in a class's `__init__` as `self.variable = ...`.
1205 """
1206 return self._filter_doc_objs(
1207 Variable, include_inherited, lambda dobj: dobj.instance_var,
1208 sort)
1210 def methods(self, include_inherited=True, sort=True) -> List['Function']:
1211 """
1212 Returns an optionally-sorted list of `pdoc.Function` objects that
1213 represent this class' methods.
1214 """
1215 return self._filter_doc_objs(
1216 Function, include_inherited, lambda dobj: dobj.is_method,
1217 sort)
1219 def functions(self, include_inherited=True, sort=True) -> List['Function']:
1220 """
1221 Returns an optionally-sorted list of `pdoc.Function` objects that
1222 represent this class' static functions.
1223 """
1224 return self._filter_doc_objs(
1225 Function, include_inherited, lambda dobj: not dobj.is_method,
1226 sort)
1228 def inherited_members(self) -> List[Tuple['Class', List[Doc]]]:
1229 """
1230 Returns all inherited members as a list of tuples
1231 (ancestor class, list of ancestor class' members sorted by name),
1232 sorted by MRO.
1233 """
1234 return sorted(((cast(Class, k), sorted(g))
1235 for k, g in groupby((i.inherits
1236 for i in self.doc.values() if i.inherits),
1237 key=lambda i: i.cls)), # type: ignore
1238 key=lambda x, _mro_index=self.mro().index: _mro_index(x[0])) # type: ignore
1240 def _fill_inheritance(self):
1241 """
1242 Traverses this class's ancestor list and attempts to fill in
1243 missing documentation objects from its ancestors.
1245 Afterwards, call to `pdoc.Class._link_inheritance()` to also
1246 set `pdoc.Doc.inherits` pointers.
1247 """
1248 super_members = self._super_members = {}
1249 for cls in self.mro(only_documented=True):
1250 for name, dobj in cls.doc.items():
1251 if name not in super_members and dobj.docstring:
1252 super_members[name] = dobj
1253 if name not in self.doc:
1254 dobj = copy(dobj)
1255 dobj.cls = self
1257 self.doc[name] = dobj
1258 self.module._context[dobj.refname] = dobj
1260 def _link_inheritance(self):
1261 """
1262 Set `pdoc.Doc.inherits` pointers to inherited ancestors' members,
1263 as appropriate. This must be called after
1264 `pdoc.Class._fill_inheritance()`.
1266 The reason this is split in two parts is that in-between
1267 the `__pdoc__` overrides are applied.
1268 """
1269 if not hasattr(self, '_super_members'):
1270 return
1272 for name, parent_dobj in self._super_members.items():
1273 try:
1274 dobj = self.doc[name]
1275 except KeyError:
1276 # There is a key in some __pdoc__ dict blocking this member
1277 continue
1278 if (dobj.obj is parent_dobj.obj or
1279 (dobj.docstring or parent_dobj.docstring) == parent_dobj.docstring):
1280 dobj.inherits = parent_dobj
1281 dobj.docstring = parent_dobj.docstring
1282 del self._super_members
1285def maybe_lru_cache(func):
1286 cached_func = lru_cache()(func)
1288 @wraps(func)
1289 def wrapper(*args):
1290 try:
1291 return cached_func(*args)
1292 except TypeError:
1293 return func(*args)
1295 return wrapper
1298@maybe_lru_cache
1299def _formatannotation(annot):
1300 """
1301 Format typing annotation with better handling of `typing.NewType`,
1302 `typing.Optional`, `nptyping.NDArray` and other types.
1304 >>> _formatannotation(NewType('MyType', str))
1305 'pdoc.MyType'
1306 >>> _formatannotation(Optional[Tuple[Optional[int], None]])
1307 'Tuple[int | None, None] | None'
1308 >>> _formatannotation(Optional[Union[int, float, None]])
1309 'int | float | None'
1310 >>> _formatannotation(Union[int, float])
1311 'int | float'
1312 >>> from typing import Callable
1313 >>> _formatannotation(Callable[[Optional[int]], float])
1314 'Callable[[int | None], float]'
1315 >>> from collections.abc import Callable
1316 >>> _formatannotation(Callable[[Optional[int]], float])
1317 'Callable[[int | None], float]'
1318 """
1319 class force_repr(str):
1320 __repr__ = str.__str__
1322 def maybe_replace_reprs(a):
1323 # NoneType -> None
1324 if a is type(None): # noqa: E721
1325 return force_repr('None')
1326 # Union[T, None] -> Optional[T]
1327 if getattr(a, '__origin__', None) is typing.Union:
1328 union_args = a.__args__
1329 is_optional = type(None) in union_args
1330 if is_optional:
1331 union_args = (x for x in union_args if x is not type(None))
1332 t = ' | '.join(inspect.formatannotation(maybe_replace_reprs(x))
1333 for x in union_args)
1334 if is_optional:
1335 t += ' | None'
1336 return force_repr(t)
1337 # typing.NewType('T', foo) -> T
1338 module = getattr(a, '__module__', '')
1339 if module == 'typing' and getattr(a, '__qualname__', '').startswith('NewType.'):
1340 return force_repr(a.__name__)
1341 # nptyping.types._ndarray.NDArray -> NDArray[(Any,), Int[64]] # GH-231
1342 if module.startswith('nptyping.'):
1343 return force_repr(repr(a))
1344 # Recurse into typing.Callable/etc. args
1345 if hasattr(a, '__args__'):
1346 if hasattr(a, 'copy_with'):
1347 if a is typing.Callable:
1348 # Bug on Python < 3.9, https://bugs.python.org/issue42195
1349 return a
1350 a = a.copy_with(tuple([maybe_replace_reprs(arg) for arg in a.__args__]))
1351 elif hasattr(a, '__origin__'):
1352 args = tuple(map(maybe_replace_reprs, a.__args__))
1353 try:
1354 a = a.__origin__[args]
1355 except TypeError:
1356 # XXX: Python 3.10-only: Convert to list since _CallableGenericAlias.__new__
1357 # currently cannot have tuples as arguments.
1358 args_in = list(args[:-1])
1359 arg_out = args[-1]
1360 # collections.abc.Callable takes "([in], out)"
1361 a = a.__origin__[(args_in, arg_out)]
1362 # Recurse into lists
1363 if isinstance(a, (list, tuple)):
1364 return type(a)(map(maybe_replace_reprs, a))
1365 # Shorten standard collections: collections.abc.Callable -> Callable
1366 if module == 'collections.abc':
1367 return force_repr(repr(a).removeprefix('collections.abc.'))
1368 return a
1370 return str(inspect.formatannotation(maybe_replace_reprs(annot)))
1373class Function(Doc):
1374 """
1375 Representation of documentation for a function or method.
1376 """
1377 def __init__(self, name: str, module: Module, obj, *, cls: Optional[Class] = None):
1378 """
1379 Same as `pdoc.Doc`, except `obj` must be a
1380 Python function object. The docstring is gathered automatically.
1382 `cls` should be set when this is a method or a static function
1383 beloing to a class. `cls` should be a `pdoc.Class` object.
1385 `method` should be `True` when the function is a method. In
1386 all other cases, it should be `False`.
1387 """
1388 assert callable(obj), (name, module, obj)
1389 super().__init__(name, module, obj)
1391 self.cls = cls
1392 """
1393 The `pdoc.Class` documentation object if the function is a method.
1394 If not, this is None.
1395 """
1397 @property
1398 def is_method(self) -> bool:
1399 """
1400 Whether this function is a normal bound method.
1402 In particular, static and class methods have this set to False.
1403 """
1404 assert self.cls
1405 return not Class._method_type(self.cls.obj, self.name)
1407 @property
1408 def method(self):
1409 warn('`Function.method` is deprecated. Use: `Function.is_method`', DeprecationWarning,
1410 stacklevel=2)
1411 return self.is_method
1413 __pdoc__['Function.method'] = False
1415 def funcdef(self) -> str:
1416 """
1417 Generates the string of keywords used to define the function,
1418 for example `def` or `async def`.
1419 """
1420 return 'async def' if self._is_async else 'def'
1422 @property
1423 def _is_async(self):
1424 """
1425 Returns whether is function is asynchronous, either as a coroutine or an async
1426 generator.
1427 """
1428 try:
1429 # Both of these are required because coroutines aren't classified as async
1430 # generators and vice versa.
1431 obj = inspect.unwrap(self.obj)
1432 return (inspect.iscoroutinefunction(obj) or
1433 inspect.isasyncgenfunction(obj))
1434 except AttributeError:
1435 return False
1437 def return_annotation(self, *, link=None) -> str:
1438 """Formatted function return type annotation or empty string if none."""
1439 annot = ''
1440 for method in (
1441 lambda: _get_type_hints(self.obj)['return'],
1442 # Mainly for non-property variables
1443 lambda: _get_type_hints(cast(Class, self.cls).obj)[self.name],
1444 # global variables
1445 lambda: _get_type_hints(not self.cls and self.module.obj)[self.name],
1446 # properties
1447 lambda: inspect.signature(_unwrap_descriptor(self)).return_annotation,
1448 # Use raw annotation strings in unmatched forward declarations
1449 lambda: cast(Class, self.cls).obj.__annotations__[self.name],
1450 # Extract annotation from the docstring for C builtin function
1451 lambda: Function._signature_from_string(self).return_annotation,
1452 ):
1453 try:
1454 annot = method()
1455 except Exception:
1456 continue
1457 else:
1458 break
1459 else:
1460 # Don't warn on variables. The annotation just isn't available.
1461 if not isinstance(self, Variable):
1462 warn(f"Error handling return annotation for {self!r}", stacklevel=3)
1464 if annot is inspect.Parameter.empty or not annot:
1465 return ''
1467 if isinstance(annot, str):
1468 s = annot
1469 else:
1470 s = _formatannotation(annot)
1471 s = re.sub(r'\bForwardRef\((?P<quot>[\"\'])(?P<str>.*?)(?P=quot)\)',
1472 r'\g<str>', s)
1473 s = s.replace(' ', '\N{NBSP}') # Better line breaks in html signatures
1475 if link:
1476 from pdoc.html_helpers import _linkify
1477 s = re.sub(r'[\w\.]+', partial(_linkify, link=link, module=self.module), s)
1478 return s
1480 def params(self, *, annotate: bool = False,
1481 link: Optional[Callable[[Doc], str]] = None) -> List[str]:
1482 """
1483 Returns a list where each element is a nicely formatted
1484 parameter of this function. This includes argument lists,
1485 keyword arguments and default values, and it doesn't include any
1486 optional arguments whose names begin with an underscore.
1488 If `annotate` is True, the parameter strings include [PEP 484]
1489 type hint annotations.
1491 [PEP 484]: https://www.python.org/dev/peps/pep-0484/
1492 """
1493 return self._params(self, annotate=annotate, link=link, module=self.module)
1495 @staticmethod
1496 def _params(doc_obj, annotate=False, link=None, module=None):
1497 try:
1498 # We want __init__ to actually be implemented somewhere in the
1499 # MRO to still satisfy https://github.com/pdoc3/pdoc/issues/124
1500 if (
1501 inspect.isclass(doc_obj.obj)
1502 and doc_obj.obj.__init__ is not object.__init__
1503 ):
1504 # Remove the first argument (self) from __init__ signature
1505 init_sig = inspect.signature(doc_obj.obj.__init__)
1506 init_params = list(init_sig.parameters.values())
1507 signature = init_sig.replace(parameters=init_params[1:])
1508 else:
1509 signature = inspect.signature(doc_obj.obj)
1510 except ValueError:
1511 signature = Function._signature_from_string(doc_obj)
1512 if not signature:
1513 return ['...']
1515 def safe_default_value(p: inspect.Parameter):
1516 value = p.default
1517 if value is inspect.Parameter.empty:
1518 return p
1520 replacement = next((i for i in ('os.environ',
1521 'sys.stdin',
1522 'sys.stdout',
1523 'sys.stderr',)
1524 if value is eval(i)), None)
1525 if not replacement:
1526 if isinstance(value, enum.Enum):
1527 replacement = str(value)
1528 elif inspect.isclass(value):
1529 replacement = f'{value.__module__ or _UNKNOWN_MODULE}.{value.__qualname__}'
1530 elif ' at 0x' in repr(value):
1531 replacement = re.sub(r' at 0x\w+', '', repr(value))
1533 nonlocal link
1534 if link and ('<' in repr(value) or '>' in repr(value)):
1535 import html
1536 replacement = html.escape(replacement or repr(value))
1538 if replacement:
1539 class mock:
1540 def __repr__(self):
1541 return replacement
1542 return p.replace(default=mock())
1543 return p
1545 params = []
1546 kw_only = False
1547 pos_only = False
1548 EMPTY = inspect.Parameter.empty
1550 if link:
1551 from pdoc.html_helpers import _linkify
1552 _linkify = partial(_linkify, link=link, module=module)
1554 for p in signature.parameters.values(): # type: inspect.Parameter
1555 if not _is_public(p.name) and p.default is not EMPTY:
1556 continue
1558 if p.kind == p.POSITIONAL_ONLY:
1559 pos_only = True
1560 elif pos_only:
1561 params.append("/")
1562 pos_only = False
1564 if p.kind == p.VAR_POSITIONAL:
1565 kw_only = True
1566 if p.kind == p.KEYWORD_ONLY and not kw_only:
1567 kw_only = True
1568 params.append('*')
1570 p = safe_default_value(p)
1572 if not annotate:
1573 p = p.replace(annotation=EMPTY)
1575 formatted = p.name
1576 if p.annotation is not EMPTY:
1577 annotation = _formatannotation(p.annotation).replace(' ', '\N{NBSP}')
1578 # "Eval" forward-declarations (typing string literals)
1579 if isinstance(p.annotation, str):
1580 annotation = annotation.strip("'")
1581 if link:
1582 annotation = re.sub(r'[\w\.]+', _linkify, annotation)
1583 formatted += f':\N{NBSP}{annotation}'
1584 if p.default is not EMPTY:
1585 if p.annotation is not EMPTY:
1586 formatted += f'\N{NBSP}=\N{NBSP}{repr(p.default)}'
1587 else:
1588 formatted += f'={repr(p.default)}'
1589 if p.kind == p.VAR_POSITIONAL:
1590 formatted = f'*{formatted}'
1591 elif p.kind == p.VAR_KEYWORD:
1592 formatted = f'**{formatted}'
1594 params.append(formatted)
1596 if pos_only:
1597 params.append("/")
1599 return params
1601 @staticmethod
1602 @lru_cache()
1603 def _signature_from_string(self):
1604 signature = None
1605 for expr, cleanup_docstring, filter in (
1606 # Full proper typed signature, such as one from pybind11
1607 (r'^{}\(.*\)(?: -> .*)?$', True, lambda s: s),
1608 # Human-readable, usage-like signature from some Python builtins
1609 # (e.g. `range` or `slice` or `itertools.repeat` or `numpy.arange`)
1610 (r'^{}\(.*\)(?= -|$)', False, lambda s: s.replace('[', '').replace(']', '')),
1611 ):
1612 strings = sorted(re.findall(expr.format(self.name),
1613 self.docstring, re.MULTILINE),
1614 key=len, reverse=True)
1615 if strings:
1616 string = filter(strings[0])
1617 _locals, _globals = {}, {}
1618 _globals.update({'capsule': None}) # pybind11 capsule data type
1619 _globals.update(typing.__dict__)
1620 _globals.update(self.module.obj.__dict__)
1621 # Trim binding module basename from type annotations
1622 # See: https://github.com/pdoc3/pdoc/pull/148#discussion_r407114141
1623 module_basename = self.module.name.rsplit('.', maxsplit=1)[-1]
1624 if module_basename in string and module_basename not in _globals:
1625 string = re.sub(fr'(?<!\.)\b{module_basename}\.\b', '', string)
1627 try:
1628 exec(f'def {string}: pass', _globals, _locals)
1629 except Exception:
1630 continue
1631 signature = inspect.signature(_locals[self.name])
1632 if cleanup_docstring and len(strings) == 1:
1633 # Remove signature from docstring variable
1634 self.docstring = self.docstring.replace(strings[0], '')
1635 break
1636 return signature
1638 @property
1639 def refname(self) -> str:
1640 return f'{self.cls.refname if self.cls else self.module.refname}.{self.name}'
1643class Variable(Doc):
1644 """
1645 Representation of a variable's documentation. This includes
1646 module, class, and instance variables.
1647 """
1648 def __init__(self, name: str, module: Module, docstring, *,
1649 obj=None, cls: Optional[Class] = None, instance_var: bool = False,
1650 kind: Literal["prop", "var"] = 'var'):
1651 """
1652 Same as `pdoc.Doc`, except `cls` should be provided
1653 as a `pdoc.Class` object when this is a class or instance
1654 variable.
1655 """
1656 super().__init__(name, module, obj, docstring)
1658 self.cls = cls
1659 """
1660 The `pdoc.Class` object if this is a class or instance
1661 variable. If not (i.e. it is a global variable), this is None.
1662 """
1664 self.instance_var = instance_var
1665 """
1666 True if variable is some class' instance variable (as
1667 opposed to class variable).
1668 """
1670 self.kind = kind
1671 """
1672 `prop` if variable is a dynamic property (has getter/setter or deleter),
1673 or `var` otherwise.
1674 """
1676 @property
1677 def qualname(self) -> str:
1678 if self.cls:
1679 return f'{self.cls.qualname}.{self.name}'
1680 return self.name
1682 @property
1683 def refname(self) -> str:
1684 return f'{self.cls.refname if self.cls else self.module.refname}.{self.name}'
1686 def type_annotation(self, *, link=None) -> str:
1687 """Formatted variable type annotation or empty string if none."""
1688 return Function.return_annotation(cast(Function, self), link=link)
1691class External(Doc):
1692 """
1693 A representation of an external identifier. The textual
1694 representation is the same as an internal identifier.
1696 External identifiers are also used to represent something that is
1697 not documented but appears somewhere in the public interface (like
1698 the ancestor list of a class).
1699 """
1701 __pdoc__["External.docstring"] = """
1702 An empty string. External identifiers do not have
1703 docstrings.
1704 """
1705 __pdoc__["External.module"] = """
1706 Always `None`. External identifiers have no associated
1707 `pdoc.Module`.
1708 """
1709 __pdoc__["External.name"] = """
1710 Always equivalent to `pdoc.External.refname` since external
1711 identifiers are always expressed in their fully qualified
1712 form.
1713 """
1715 def __init__(self, name: str):
1716 """
1717 Initializes an external identifier with `name`, where `name`
1718 should be a fully qualified name.
1719 """
1720 super().__init__(name, None, None)
1722 def url(self, *args, **kwargs):
1723 """
1724 `External` objects return absolute urls matching `/{name}.ext`.
1725 """
1726 return f'/{self.name}.ext'