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

834 statements  

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. 

7 

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 

32 

33from mako.lookup import TemplateLookup 

34from mako.exceptions import TopLevelLookupException 

35from mako.template import Template 

36 

37try: 

38 from pdoc._version import version as __version__ # noqa: F401 

39except ImportError: 

40 __version__ = '???' # Package not installed 

41 

42 

43_get_type_hints = lru_cache()(typing.get_type_hints) 

44 

45_URL_MODULE_SUFFIX = '.html' 

46_URL_INDEX_MODULE_SUFFIX = '.m.html' # For modules named literal 'index' 

47_URL_PACKAGE_SUFFIX = '/index.html' 

48 

49# type.__module__ can be None by the Python spec. In those cases, use this value 

50_UNKNOWN_MODULE = '?' 

51 

52T = TypeVar('T', 'Module', 'Class', 'Function', 'Variable') 

53 

54__pdoc__: Dict[str, Union[bool, str]] = {} 

55 

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

69 

70 

71class Context(dict): 

72 """ 

73 The context object that maps all documented identifiers 

74 (`pdoc.Doc.refname`) to their respective `pdoc.Doc` objects. 

75 

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. 

79 

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 

84 

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

91 

92 

93_global_context = Context() 

94 

95 

96def reset(): 

97 """Resets the global `pdoc.Context` to the initial (empty) state.""" 

98 global _global_context 

99 _global_context.clear() 

100 

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

112 

113 

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) 

124 

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) 

133 

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

139 

140 return config 

141 

142 

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) 

149 

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

155 

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 

163 

164 

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. 

169 

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) 

179 

180 

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. 

186 

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) 

196 

197 

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) 

222 

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) 

235 

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 

246 

247 

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) 

253 

254 

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

261 

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 {}, {} 

269 

270 vars: Dict[str, str] = {} 

271 instance_vars: Dict[str, str] = {} 

272 

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 {}, {} 

288 

289 if isinstance(doc_obj, Class): 

290 tree = tree.body[0] # ast.parse creates a dummy ast.Module wrapper 

291 

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 

298 

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 

309 

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 

319 

320 if not _is_public(name) and not _is_whitelisted(name, doc_obj): 

321 return None 

322 

323 return name 

324 

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 

331 

332 name = get_name(assign_node) 

333 if not name: 

334 continue 

335 

336 docstring = inspect.cleandoc(str_node.value.value).strip() 

337 if not docstring: 

338 continue 

339 

340 vars[name] = docstring 

341 

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 

346 

347 name = get_name(assign_node) 

348 if not name: 

349 continue 

350 

351 # Already documented. PEP-224 method above takes precedence. 

352 if name in vars: 

353 continue 

354 

355 def get_indent(line): 

356 return len(line) - len(line.lstrip()) 

357 

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 

368 

369 # Since we went 'up' need to reverse lines to be in correct order 

370 comment_lines = comment_lines[::-1] 

371 

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

375 

376 if comment_lines: 

377 vars[name] = '\n'.join(comment_lines) 

378 

379 return vars, instance_vars 

380 

381 

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 

396 

397 

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 

412 

413 

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("_") 

420 

421 

422def _is_function(obj): 

423 return inspect.isroutine(obj) and callable(obj) and not isinstance(obj, Mock) # Mock: GH-350 

424 

425 

426def _is_descriptor(obj): 

427 return (inspect.isdatadescriptor(obj) or 

428 inspect.ismethoddescriptor(obj) or 

429 inspect.isgetsetdescriptor(obj) or 

430 inspect.ismemberdescriptor(obj)) 

431 

432 

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) 

450 

451 

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

460 

461 

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

481 

482 

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

488 

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 

494 

495 graph = {cls: set(cls.mro(only_documented=True)) 

496 for cls in _filter_type(Class, context)} 

497 

498 for cls in _toposort(graph): 

499 cls._fill_inheritance() 

500 

501 for module in _filter_type(Module, context): 

502 module._link_inheritance() 

503 

504 

505class Doc: 

506 """ 

507 A base class for all documentation objects. 

508 

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. 

514 

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

521 

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

534 

535 self.name = name 

536 """ 

537 The identifier name for this object. 

538 """ 

539 

540 self.obj = obj 

541 """ 

542 The raw python object. 

543 """ 

544 

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

554 

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

560 

561 def __repr__(self): 

562 return f'<{self.__class__.__name__} {self.refname!r}>' 

563 

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

576 

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 

587 

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) 

595 

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. 

602 

603 Specify `relative_to` (a `pdoc.Module` object) to obtain a 

604 relative URL. 

605 

606 For usage of `link_prefix` see `pdoc.html()`. 

607 

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

613 

614 if relative_to is None or link_prefix: 

615 return link_prefix + self._url() 

616 

617 if self.module.name == relative_to.name: 

618 return f'#{self.refname}' 

619 

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 

626 

627 def _url(self): 

628 return f'{self.module._url()}#{self.refname}' 

629 

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 

638 

639 def __lt__(self, other): 

640 return self.refname < other.refname 

641 

642 

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

651 

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. 

660 

661 `docfilter` is an optional predicate that controls which 

662 sub-objects are documentated (see also: `pdoc.html()`). 

663 

664 `supermodule` is the parent `pdoc.Module` this module is 

665 a submodule of. 

666 

667 `context` is an instance of `pdoc.Context`. If `None` a 

668 global context object will be used. 

669 

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) 

676 

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__')] 

680 

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' 

688 

689 self.supermodule = supermodule 

690 """ 

691 The parent `pdoc.Module` this module is a submodule of, or `None`. 

692 """ 

693 

694 self.doc: Dict[str, Union[Module, Class, Function, Variable]] = {} 

695 """A mapping from identifier name to a documentation object.""" 

696 

697 self._is_inheritance_linked = False 

698 """Re-entry guard for `pdoc.Module._link_inheritance()`.""" 

699 

700 self._skipped_submodules = set() 

701 

702 var_docstrings, _ = _pep224_docstrings(self) 

703 

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__ 

721 

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

728 

729 if _is_blacklisted(name, self): 

730 self._context.blacklisted.add(f'{self.refname}.{name}') 

731 continue 

732 

733 obj = inspect.unwrap(obj) 

734 public_objs.append((name, obj)) 

735 

736 index = list(self.obj.__dict__).index 

737 public_objs.sort(key=lambda i: index(i[0])) 

738 

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) 

746 

747 # If the module is a package, scan the directory for submodules 

748 if self.is_package: 

749 

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 

769 

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 

774 

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 

781 

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) 

787 

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) 

794 

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) 

801 

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

809 

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

815 

816 __pdoc__['Module.ImportWarning'] = False 

817 

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__', {}) 

822 

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. 

828 

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 

834 

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 

840 

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}).') 

847 

848 if name in self._skipped_submodules: 

849 continue 

850 

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

857 

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) 

864 

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] 

869 

870 continue 

871 

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) 

879 

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

884 

885 self._is_inheritance_linked = True 

886 

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 

896 

897 def html(self, minify=True, **kwargs) -> str: 

898 """ 

899 Returns the documentation for this module as 

900 self-contained HTML. 

901 

902 If `minify` is `True`, the resulting HTML is minified. 

903 

904 For explanation of other arguments, see `pdoc.html()`. 

905 

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 

915 

916 @property 

917 def is_package(self) -> bool: 

918 """ 

919 `True` if this module is a package. 

920 

921 Works by checking whether the module has a `__path__` attribute. 

922 """ 

923 return hasattr(self.obj, "__path__") 

924 

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 

934 

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__}') 

944 

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. 

950 

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 

956 

957 if _name.endswith('.__init__'): # Ref to class' init is ref to class itself 

958 _name = _name[:-len('.__init__')] 

959 

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

964 

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 

968 

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) 

975 

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) 

982 

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) 

989 

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) 

996 

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 

1004 

1005 

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

1015 

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 

1032 

1033 

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) 

1040 

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

1046 

1047 super().__init__(name, module, obj, docstring=docstring) 

1048 

1049 self.doc: Dict[str, Union[Function, Variable]] = {} 

1050 """A mapping from identifier name to a `pdoc.Doc` objects.""" 

1051 

1052 # Annotations for filtering. 

1053 # Use only own, non-inherited annotations (the rest will be inherited) 

1054 annotations = getattr(self.obj, '__annotations__', {}) 

1055 

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

1064 

1065 if _is_blacklisted(_name, self): 

1066 self.module._context.blacklisted.add(f'{self.refname}.{_name}') 

1067 continue 

1068 

1069 obj = inspect.unwrap(obj) 

1070 public_objs.append((_name, obj)) 

1071 

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 

1084 

1085 public_objs.sort(key=lambda i: definition_order_index(i[0])) 

1086 

1087 var_docstrings, instance_var_docstrings = _pep224_docstrings(self) 

1088 

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

1106 

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) 

1112 

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

1131 

1132 @property 

1133 def refname(self) -> str: 

1134 return f'{self.module.name}.{self.qualname}' 

1135 

1136 def mro(self, only_documented=False) -> List['Class']: 

1137 """ 

1138 Returns a list of ancestor (superclass) documentation objects 

1139 in method resolution order. 

1140 

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 

1158 

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__()`). 

1163 

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

1169 

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

1181 

1182 return Function._params(self, annotate=annotate, link=link, module=self.module) 

1183 

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 

1190 

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) 

1199 

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) 

1209 

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) 

1218 

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) 

1227 

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 

1239 

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. 

1244 

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 

1256 

1257 self.doc[name] = dobj 

1258 self.module._context[dobj.refname] = dobj 

1259 

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()`. 

1265 

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 

1271 

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 

1283 

1284 

1285def maybe_lru_cache(func): 

1286 cached_func = lru_cache()(func) 

1287 

1288 @wraps(func) 

1289 def wrapper(*args): 

1290 try: 

1291 return cached_func(*args) 

1292 except TypeError: 

1293 return func(*args) 

1294 

1295 return wrapper 

1296 

1297 

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. 

1303 

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__ 

1321 

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 

1369 

1370 return str(inspect.formatannotation(maybe_replace_reprs(annot))) 

1371 

1372 

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. 

1381 

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. 

1384 

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) 

1390 

1391 self.cls = cls 

1392 """ 

1393 The `pdoc.Class` documentation object if the function is a method. 

1394 If not, this is None. 

1395 """ 

1396 

1397 @property 

1398 def is_method(self) -> bool: 

1399 """ 

1400 Whether this function is a normal bound method. 

1401 

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) 

1406 

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 

1412 

1413 __pdoc__['Function.method'] = False 

1414 

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' 

1421 

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 

1436 

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) 

1463 

1464 if annot is inspect.Parameter.empty or not annot: 

1465 return '' 

1466 

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 

1474 

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 

1479 

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. 

1487 

1488 If `annotate` is True, the parameter strings include [PEP 484] 

1489 type hint annotations. 

1490 

1491 [PEP 484]: https://www.python.org/dev/peps/pep-0484/ 

1492 """ 

1493 return self._params(self, annotate=annotate, link=link, module=self.module) 

1494 

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 ['...'] 

1514 

1515 def safe_default_value(p: inspect.Parameter): 

1516 value = p.default 

1517 if value is inspect.Parameter.empty: 

1518 return p 

1519 

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

1532 

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

1537 

1538 if replacement: 

1539 class mock: 

1540 def __repr__(self): 

1541 return replacement 

1542 return p.replace(default=mock()) 

1543 return p 

1544 

1545 params = [] 

1546 kw_only = False 

1547 pos_only = False 

1548 EMPTY = inspect.Parameter.empty 

1549 

1550 if link: 

1551 from pdoc.html_helpers import _linkify 

1552 _linkify = partial(_linkify, link=link, module=module) 

1553 

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 

1557 

1558 if p.kind == p.POSITIONAL_ONLY: 

1559 pos_only = True 

1560 elif pos_only: 

1561 params.append("/") 

1562 pos_only = False 

1563 

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('*') 

1569 

1570 p = safe_default_value(p) 

1571 

1572 if not annotate: 

1573 p = p.replace(annotation=EMPTY) 

1574 

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

1593 

1594 params.append(formatted) 

1595 

1596 if pos_only: 

1597 params.append("/") 

1598 

1599 return params 

1600 

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) 

1626 

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 

1637 

1638 @property 

1639 def refname(self) -> str: 

1640 return f'{self.cls.refname if self.cls else self.module.refname}.{self.name}' 

1641 

1642 

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) 

1657 

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

1663 

1664 self.instance_var = instance_var 

1665 """ 

1666 True if variable is some class' instance variable (as 

1667 opposed to class variable). 

1668 """ 

1669 

1670 self.kind = kind 

1671 """ 

1672 `prop` if variable is a dynamic property (has getter/setter or deleter), 

1673 or `var` otherwise. 

1674 """ 

1675 

1676 @property 

1677 def qualname(self) -> str: 

1678 if self.cls: 

1679 return f'{self.cls.qualname}.{self.name}' 

1680 return self.name 

1681 

1682 @property 

1683 def refname(self) -> str: 

1684 return f'{self.cls.refname if self.cls else self.module.refname}.{self.name}' 

1685 

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) 

1689 

1690 

1691class External(Doc): 

1692 """ 

1693 A representation of an external identifier. The textual 

1694 representation is the same as an internal identifier. 

1695 

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

1700 

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

1714 

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) 

1721 

1722 def url(self, *args, **kwargs): 

1723 """ 

1724 `External` objects return absolute urls matching `/{name}.ext`. 

1725 """ 

1726 return f'/{self.name}.ext'