1""" 2 sphinx.domains.python 3 ~~~~~~~~~~~~~~~~~~~~~ 4 5 The Python domain. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import builtins 12import inspect 13import re 14import sys 15import typing 16import warnings 17from inspect import Parameter 18from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple, cast 19 20from docutils import nodes 21from docutils.nodes import Element, Node 22from docutils.parsers.rst import directives 23 24from sphinx import addnodes 25from sphinx.addnodes import desc_signature, pending_xref 26from sphinx.application import Sphinx 27from sphinx.builders import Builder 28from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning 29from sphinx.directives import ObjectDescription 30from sphinx.domains import Domain, Index, IndexEntry, ObjType 31from sphinx.environment import BuildEnvironment 32from sphinx.locale import _, __ 33from sphinx.pycode.ast import ast 34from sphinx.pycode.ast import parse as ast_parse 35from sphinx.roles import XRefRole 36from sphinx.util import logging 37from sphinx.util.docfields import Field, GroupedField, TypedField 38from sphinx.util.docutils import SphinxDirective 39from sphinx.util.inspect import signature_from_str 40from sphinx.util.nodes import make_id, make_refnode 41from sphinx.util.typing import TextlikeNode 42 43if False: 44 # For type annotation 45 from typing import Type # for python3.5.1 46 47 48logger = logging.getLogger(__name__) 49 50 51# REs for Python signatures 52py_sig_re = re.compile( 53 r'''^ ([\w.]*\.)? # class name(s) 54 (\w+) \s* # thing name 55 (?: \(\s*(.*)\s*\) # optional: arguments 56 (?:\s* -> \s* (.*))? # return annotation 57 )? $ # and nothing more 58 ''', re.VERBOSE) 59 60 61pairindextypes = { 62 'module': _('module'), 63 'keyword': _('keyword'), 64 'operator': _('operator'), 65 'object': _('object'), 66 'exception': _('exception'), 67 'statement': _('statement'), 68 'builtin': _('built-in function'), 69} 70 71ObjectEntry = NamedTuple('ObjectEntry', [('docname', str), 72 ('node_id', str), 73 ('objtype', str)]) 74ModuleEntry = NamedTuple('ModuleEntry', [('docname', str), 75 ('node_id', str), 76 ('synopsis', str), 77 ('platform', str), 78 ('deprecated', bool)]) 79 80 81def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref: 82 """Convert a type string to a cross reference node.""" 83 if text == 'None': 84 reftype = 'obj' 85 else: 86 reftype = 'class' 87 88 if env: 89 kwargs = {'py:module': env.ref_context.get('py:module'), 90 'py:class': env.ref_context.get('py:class')} 91 else: 92 kwargs = {} 93 94 return pending_xref('', nodes.Text(text), 95 refdomain='py', reftype=reftype, reftarget=text, **kwargs) 96 97 98def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]: 99 """Parse type annotation.""" 100 def unparse(node: ast.AST) -> List[Node]: 101 if isinstance(node, ast.Attribute): 102 return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))] 103 elif isinstance(node, ast.BinOp): 104 result = unparse(node.left) # type: List[Node] 105 result.extend(unparse(node.op)) 106 result.extend(unparse(node.right)) 107 return result 108 elif isinstance(node, ast.BitOr): 109 return [nodes.Text(' '), addnodes.desc_sig_punctuation('', '|'), nodes.Text(' ')] 110 elif isinstance(node, ast.Expr): 111 return unparse(node.value) 112 elif isinstance(node, ast.Index): 113 return unparse(node.value) 114 elif isinstance(node, ast.List): 115 result = [addnodes.desc_sig_punctuation('', '[')] 116 for elem in node.elts: 117 result.extend(unparse(elem)) 118 result.append(addnodes.desc_sig_punctuation('', ', ')) 119 result.pop() 120 result.append(addnodes.desc_sig_punctuation('', ']')) 121 return result 122 elif isinstance(node, ast.Module): 123 return sum((unparse(e) for e in node.body), []) 124 elif isinstance(node, ast.Name): 125 return [nodes.Text(node.id)] 126 elif isinstance(node, ast.Subscript): 127 result = unparse(node.value) 128 result.append(addnodes.desc_sig_punctuation('', '[')) 129 result.extend(unparse(node.slice)) 130 result.append(addnodes.desc_sig_punctuation('', ']')) 131 return result 132 elif isinstance(node, ast.Tuple): 133 if node.elts: 134 result = [] 135 for elem in node.elts: 136 result.extend(unparse(elem)) 137 result.append(addnodes.desc_sig_punctuation('', ', ')) 138 result.pop() 139 else: 140 result = [addnodes.desc_sig_punctuation('', '('), 141 addnodes.desc_sig_punctuation('', ')')] 142 143 return result 144 else: 145 if sys.version_info >= (3, 6): 146 if isinstance(node, ast.Constant): 147 if node.value is Ellipsis: 148 return [addnodes.desc_sig_punctuation('', "...")] 149 else: 150 return [nodes.Text(node.value)] 151 152 if sys.version_info < (3, 8): 153 if isinstance(node, ast.Ellipsis): 154 return [addnodes.desc_sig_punctuation('', "...")] 155 elif isinstance(node, ast.NameConstant): 156 return [nodes.Text(node.value)] 157 158 raise SyntaxError # unsupported syntax 159 160 if env is None: 161 warnings.warn("The env parameter for _parse_annotation becomes required now.", 162 RemovedInSphinx50Warning, stacklevel=2) 163 164 try: 165 tree = ast_parse(annotation) 166 result = unparse(tree) 167 for i, node in enumerate(result): 168 if isinstance(node, nodes.Text) and node.strip(): 169 result[i] = type_to_xref(str(node), env) 170 return result 171 except SyntaxError: 172 return [type_to_xref(annotation, env)] 173 174 175def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_parameterlist: 176 """Parse a list of arguments using AST parser""" 177 params = addnodes.desc_parameterlist(arglist) 178 sig = signature_from_str('(%s)' % arglist) 179 last_kind = None 180 for param in sig.parameters.values(): 181 if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY: 182 # PEP-570: Separator for Positional Only Parameter: / 183 params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/')) 184 if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD, 185 param.POSITIONAL_ONLY, 186 None): 187 # PEP-3102: Separator for Keyword Only Parameter: * 188 params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '*')) 189 190 node = addnodes.desc_parameter() 191 if param.kind == param.VAR_POSITIONAL: 192 node += addnodes.desc_sig_operator('', '*') 193 node += addnodes.desc_sig_name('', param.name) 194 elif param.kind == param.VAR_KEYWORD: 195 node += addnodes.desc_sig_operator('', '**') 196 node += addnodes.desc_sig_name('', param.name) 197 else: 198 node += addnodes.desc_sig_name('', param.name) 199 200 if param.annotation is not param.empty: 201 children = _parse_annotation(param.annotation, env) 202 node += addnodes.desc_sig_punctuation('', ':') 203 node += nodes.Text(' ') 204 node += addnodes.desc_sig_name('', '', *children) # type: ignore 205 if param.default is not param.empty: 206 if param.annotation is not param.empty: 207 node += nodes.Text(' ') 208 node += addnodes.desc_sig_operator('', '=') 209 node += nodes.Text(' ') 210 else: 211 node += addnodes.desc_sig_operator('', '=') 212 node += nodes.inline('', param.default, classes=['default_value'], 213 support_smartquotes=False) 214 215 params += node 216 last_kind = param.kind 217 218 if last_kind == Parameter.POSITIONAL_ONLY: 219 # PEP-570: Separator for Positional Only Parameter: / 220 params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/')) 221 222 return params 223 224 225def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: 226 """"Parse" a list of arguments separated by commas. 227 228 Arguments can have "optional" annotations given by enclosing them in 229 brackets. Currently, this will split at any comma, even if it's inside a 230 string literal (e.g. default argument value). 231 """ 232 paramlist = addnodes.desc_parameterlist() 233 stack = [paramlist] # type: List[Element] 234 try: 235 for argument in arglist.split(','): 236 argument = argument.strip() 237 ends_open = ends_close = 0 238 while argument.startswith('['): 239 stack.append(addnodes.desc_optional()) 240 stack[-2] += stack[-1] 241 argument = argument[1:].strip() 242 while argument.startswith(']'): 243 stack.pop() 244 argument = argument[1:].strip() 245 while argument.endswith(']') and not argument.endswith('[]'): 246 ends_close += 1 247 argument = argument[:-1].strip() 248 while argument.endswith('['): 249 ends_open += 1 250 argument = argument[:-1].strip() 251 if argument: 252 stack[-1] += addnodes.desc_parameter(argument, argument) 253 while ends_open: 254 stack.append(addnodes.desc_optional()) 255 stack[-2] += stack[-1] 256 ends_open -= 1 257 while ends_close: 258 stack.pop() 259 ends_close -= 1 260 if len(stack) != 1: 261 raise IndexError 262 except IndexError: 263 # if there are too few or too many elements on the stack, just give up 264 # and treat the whole argument list as one argument, discarding the 265 # already partially populated paramlist node 266 paramlist = addnodes.desc_parameterlist() 267 paramlist += addnodes.desc_parameter(arglist, arglist) 268 signode += paramlist 269 else: 270 signode += paramlist 271 272 273# This override allows our inline type specifiers to behave like :class: link 274# when it comes to handling "." and "~" prefixes. 275class PyXrefMixin: 276 def make_xref(self, rolename: str, domain: str, target: str, 277 innernode: "Type[TextlikeNode]" = nodes.emphasis, 278 contnode: Node = None, env: BuildEnvironment = None) -> Node: 279 result = super().make_xref(rolename, domain, target, # type: ignore 280 innernode, contnode, env) 281 result['refspecific'] = True 282 result['py:module'] = env.ref_context.get('py:module') 283 result['py:class'] = env.ref_context.get('py:class') 284 if target.startswith(('.', '~')): 285 prefix, result['reftarget'] = target[0], target[1:] 286 if prefix == '.': 287 text = target[1:] 288 elif prefix == '~': 289 text = target.split('.')[-1] 290 for node in result.traverse(nodes.Text): 291 node.parent[node.parent.index(node)] = nodes.Text(text) 292 break 293 return result 294 295 def make_xrefs(self, rolename: str, domain: str, target: str, 296 innernode: "Type[TextlikeNode]" = nodes.emphasis, 297 contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: 298 delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)' 299 delims_re = re.compile(delims) 300 sub_targets = re.split(delims, target) 301 302 split_contnode = bool(contnode and contnode.astext() == target) 303 304 results = [] 305 for sub_target in filter(None, sub_targets): 306 if split_contnode: 307 contnode = nodes.Text(sub_target) 308 309 if delims_re.match(sub_target): 310 results.append(contnode or innernode(sub_target, sub_target)) 311 else: 312 results.append(self.make_xref(rolename, domain, sub_target, 313 innernode, contnode, env)) 314 315 return results 316 317 318class PyField(PyXrefMixin, Field): 319 def make_xref(self, rolename: str, domain: str, target: str, 320 innernode: "Type[TextlikeNode]" = nodes.emphasis, 321 contnode: Node = None, env: BuildEnvironment = None) -> Node: 322 if rolename == 'class' and target == 'None': 323 # None is not a type, so use obj role instead. 324 rolename = 'obj' 325 326 return super().make_xref(rolename, domain, target, innernode, contnode, env) 327 328 329class PyGroupedField(PyXrefMixin, GroupedField): 330 pass 331 332 333class PyTypedField(PyXrefMixin, TypedField): 334 def make_xref(self, rolename: str, domain: str, target: str, 335 innernode: "Type[TextlikeNode]" = nodes.emphasis, 336 contnode: Node = None, env: BuildEnvironment = None) -> Node: 337 if rolename == 'class' and target == 'None': 338 # None is not a type, so use obj role instead. 339 rolename = 'obj' 340 341 return super().make_xref(rolename, domain, target, innernode, contnode, env) 342 343 344class PyObject(ObjectDescription[Tuple[str, str]]): 345 """ 346 Description of a general Python object. 347 348 :cvar allow_nesting: Class is an object that allows for nested namespaces 349 :vartype allow_nesting: bool 350 """ 351 option_spec = { 352 'noindex': directives.flag, 353 'noindexentry': directives.flag, 354 'module': directives.unchanged, 355 'annotation': directives.unchanged, 356 } 357 358 doc_field_types = [ 359 PyTypedField('parameter', label=_('Parameters'), 360 names=('param', 'parameter', 'arg', 'argument', 361 'keyword', 'kwarg', 'kwparam'), 362 typerolename='class', typenames=('paramtype', 'type'), 363 can_collapse=True), 364 PyTypedField('variable', label=_('Variables'), rolename='obj', 365 names=('var', 'ivar', 'cvar'), 366 typerolename='class', typenames=('vartype',), 367 can_collapse=True), 368 PyGroupedField('exceptions', label=_('Raises'), rolename='exc', 369 names=('raises', 'raise', 'exception', 'except'), 370 can_collapse=True), 371 Field('returnvalue', label=_('Returns'), has_arg=False, 372 names=('returns', 'return')), 373 PyField('returntype', label=_('Return type'), has_arg=False, 374 names=('rtype',), bodyrolename='class'), 375 ] 376 377 allow_nesting = False 378 379 def get_signature_prefix(self, sig: str) -> str: 380 """May return a prefix to put before the object name in the 381 signature. 382 """ 383 return '' 384 385 def needs_arglist(self) -> bool: 386 """May return true if an empty argument list is to be generated even if 387 the document contains none. 388 """ 389 return False 390 391 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 392 """Transform a Python signature into RST nodes. 393 394 Return (fully qualified name of the thing, classname if any). 395 396 If inside a class, the current class name is handled intelligently: 397 * it is stripped from the displayed name if present 398 * it is added to the full name (return value) if not present 399 """ 400 m = py_sig_re.match(sig) 401 if m is None: 402 raise ValueError 403 prefix, name, arglist, retann = m.groups() 404 405 # determine module and class name (if applicable), as well as full name 406 modname = self.options.get('module', self.env.ref_context.get('py:module')) 407 classname = self.env.ref_context.get('py:class') 408 if classname: 409 add_module = False 410 if prefix and (prefix == classname or 411 prefix.startswith(classname + ".")): 412 fullname = prefix + name 413 # class name is given again in the signature 414 prefix = prefix[len(classname):].lstrip('.') 415 elif prefix: 416 # class name is given in the signature, but different 417 # (shouldn't happen) 418 fullname = classname + '.' + prefix + name 419 else: 420 # class name is not given in the signature 421 fullname = classname + '.' + name 422 else: 423 add_module = True 424 if prefix: 425 classname = prefix.rstrip('.') 426 fullname = prefix + name 427 else: 428 classname = '' 429 fullname = name 430 431 signode['module'] = modname 432 signode['class'] = classname 433 signode['fullname'] = fullname 434 435 sig_prefix = self.get_signature_prefix(sig) 436 if sig_prefix: 437 signode += addnodes.desc_annotation(sig_prefix, sig_prefix) 438 439 if prefix: 440 signode += addnodes.desc_addname(prefix, prefix) 441 elif add_module and self.env.config.add_module_names: 442 if modname and modname != 'exceptions': 443 # exceptions are a special case, since they are documented in the 444 # 'exceptions' module. 445 nodetext = modname + '.' 446 signode += addnodes.desc_addname(nodetext, nodetext) 447 448 signode += addnodes.desc_name(name, name) 449 if arglist: 450 try: 451 signode += _parse_arglist(arglist, self.env) 452 except SyntaxError: 453 # fallback to parse arglist original parser. 454 # it supports to represent optional arguments (ex. "func(foo [, bar])") 455 _pseudo_parse_arglist(signode, arglist) 456 except NotImplementedError as exc: 457 logger.warning("could not parse arglist (%r): %s", arglist, exc, 458 location=signode) 459 _pseudo_parse_arglist(signode, arglist) 460 else: 461 if self.needs_arglist(): 462 # for callables, add an empty parameter list 463 signode += addnodes.desc_parameterlist() 464 465 if retann: 466 children = _parse_annotation(retann, self.env) 467 signode += addnodes.desc_returns(retann, '', *children) 468 469 anno = self.options.get('annotation') 470 if anno: 471 signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) 472 473 return fullname, prefix 474 475 def get_index_text(self, modname: str, name: Tuple[str, str]) -> str: 476 """Return the text for the index entry of the object.""" 477 raise NotImplementedError('must be implemented in subclasses') 478 479 def add_target_and_index(self, name_cls: Tuple[str, str], sig: str, 480 signode: desc_signature) -> None: 481 modname = self.options.get('module', self.env.ref_context.get('py:module')) 482 fullname = (modname + '.' if modname else '') + name_cls[0] 483 node_id = make_id(self.env, self.state.document, '', fullname) 484 signode['ids'].append(node_id) 485 486 # Assign old styled node_id(fullname) not to break old hyperlinks (if possible) 487 # Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning) 488 if node_id != fullname and fullname not in self.state.document.ids: 489 signode['ids'].append(fullname) 490 491 self.state.document.note_explicit_target(signode) 492 493 domain = cast(PythonDomain, self.env.get_domain('py')) 494 domain.note_object(fullname, self.objtype, node_id, location=signode) 495 496 if 'noindexentry' not in self.options: 497 indextext = self.get_index_text(modname, name_cls) 498 if indextext: 499 self.indexnode['entries'].append(('single', indextext, node_id, '', None)) 500 501 def before_content(self) -> None: 502 """Handle object nesting before content 503 504 :py:class:`PyObject` represents Python language constructs. For 505 constructs that are nestable, such as a Python classes, this method will 506 build up a stack of the nesting hierarchy so that it can be later 507 de-nested correctly, in :py:meth:`after_content`. 508 509 For constructs that aren't nestable, the stack is bypassed, and instead 510 only the most recent object is tracked. This object prefix name will be 511 removed with :py:meth:`after_content`. 512 """ 513 prefix = None 514 if self.names: 515 # fullname and name_prefix come from the `handle_signature` method. 516 # fullname represents the full object name that is constructed using 517 # object nesting and explicit prefixes. `name_prefix` is the 518 # explicit prefix given in a signature 519 (fullname, name_prefix) = self.names[-1] 520 if self.allow_nesting: 521 prefix = fullname 522 elif name_prefix: 523 prefix = name_prefix.strip('.') 524 if prefix: 525 self.env.ref_context['py:class'] = prefix 526 if self.allow_nesting: 527 classes = self.env.ref_context.setdefault('py:classes', []) 528 classes.append(prefix) 529 if 'module' in self.options: 530 modules = self.env.ref_context.setdefault('py:modules', []) 531 modules.append(self.env.ref_context.get('py:module')) 532 self.env.ref_context['py:module'] = self.options['module'] 533 534 def after_content(self) -> None: 535 """Handle object de-nesting after content 536 537 If this class is a nestable object, removing the last nested class prefix 538 ends further nesting in the object. 539 540 If this class is not a nestable object, the list of classes should not 541 be altered as we didn't affect the nesting levels in 542 :py:meth:`before_content`. 543 """ 544 classes = self.env.ref_context.setdefault('py:classes', []) 545 if self.allow_nesting: 546 try: 547 classes.pop() 548 except IndexError: 549 pass 550 self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0 551 else None) 552 if 'module' in self.options: 553 modules = self.env.ref_context.setdefault('py:modules', []) 554 if modules: 555 self.env.ref_context['py:module'] = modules.pop() 556 else: 557 self.env.ref_context.pop('py:module') 558 559 560class PyModulelevel(PyObject): 561 """ 562 Description of an object on module level (functions, data). 563 """ 564 565 def run(self) -> List[Node]: 566 for cls in self.__class__.__mro__: 567 if cls.__name__ != 'DirectiveAdapter': 568 warnings.warn('PyModulelevel is deprecated. ' 569 'Please check the implementation of %s' % cls, 570 RemovedInSphinx40Warning, stacklevel=2) 571 break 572 else: 573 warnings.warn('PyModulelevel is deprecated', 574 RemovedInSphinx40Warning, stacklevel=2) 575 576 return super().run() 577 578 def needs_arglist(self) -> bool: 579 return self.objtype == 'function' 580 581 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 582 if self.objtype == 'function': 583 if not modname: 584 return _('%s() (built-in function)') % name_cls[0] 585 return _('%s() (in module %s)') % (name_cls[0], modname) 586 elif self.objtype == 'data': 587 if not modname: 588 return _('%s (built-in variable)') % name_cls[0] 589 return _('%s (in module %s)') % (name_cls[0], modname) 590 else: 591 return '' 592 593 594class PyFunction(PyObject): 595 """Description of a function.""" 596 597 option_spec = PyObject.option_spec.copy() 598 option_spec.update({ 599 'async': directives.flag, 600 }) 601 602 def get_signature_prefix(self, sig: str) -> str: 603 if 'async' in self.options: 604 return 'async ' 605 else: 606 return '' 607 608 def needs_arglist(self) -> bool: 609 return True 610 611 def add_target_and_index(self, name_cls: Tuple[str, str], sig: str, 612 signode: desc_signature) -> None: 613 super().add_target_and_index(name_cls, sig, signode) 614 if 'noindexentry' not in self.options: 615 modname = self.options.get('module', self.env.ref_context.get('py:module')) 616 node_id = signode['ids'][0] 617 618 name, cls = name_cls 619 if modname: 620 text = _('%s() (in module %s)') % (name, modname) 621 self.indexnode['entries'].append(('single', text, node_id, '', None)) 622 else: 623 text = '%s; %s()' % (pairindextypes['builtin'], name) 624 self.indexnode['entries'].append(('pair', text, node_id, '', None)) 625 626 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 627 # add index in own add_target_and_index() instead. 628 return None 629 630 631class PyDecoratorFunction(PyFunction): 632 """Description of a decorator.""" 633 634 def run(self) -> List[Node]: 635 # a decorator function is a function after all 636 self.name = 'py:function' 637 return super().run() 638 639 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 640 ret = super().handle_signature(sig, signode) 641 signode.insert(0, addnodes.desc_addname('@', '@')) 642 return ret 643 644 def needs_arglist(self) -> bool: 645 return False 646 647 648class PyVariable(PyObject): 649 """Description of a variable.""" 650 651 option_spec = PyObject.option_spec.copy() 652 option_spec.update({ 653 'type': directives.unchanged, 654 'value': directives.unchanged, 655 }) 656 657 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 658 fullname, prefix = super().handle_signature(sig, signode) 659 660 typ = self.options.get('type') 661 if typ: 662 annotations = _parse_annotation(typ, self.env) 663 signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) 664 665 value = self.options.get('value') 666 if value: 667 signode += addnodes.desc_annotation(value, ' = ' + value) 668 669 return fullname, prefix 670 671 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 672 name, cls = name_cls 673 if modname: 674 return _('%s (in module %s)') % (name, modname) 675 else: 676 return _('%s (built-in variable)') % name 677 678 679class PyClasslike(PyObject): 680 """ 681 Description of a class-like object (classes, interfaces, exceptions). 682 """ 683 684 option_spec = PyObject.option_spec.copy() 685 option_spec.update({ 686 'final': directives.flag, 687 }) 688 689 allow_nesting = True 690 691 def get_signature_prefix(self, sig: str) -> str: 692 if 'final' in self.options: 693 return 'final %s ' % self.objtype 694 else: 695 return '%s ' % self.objtype 696 697 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 698 if self.objtype == 'class': 699 if not modname: 700 return _('%s (built-in class)') % name_cls[0] 701 return _('%s (class in %s)') % (name_cls[0], modname) 702 elif self.objtype == 'exception': 703 return name_cls[0] 704 else: 705 return '' 706 707 708class PyClassmember(PyObject): 709 """ 710 Description of a class member (methods, attributes). 711 """ 712 713 def run(self) -> List[Node]: 714 for cls in self.__class__.__mro__: 715 if cls.__name__ != 'DirectiveAdapter': 716 warnings.warn('PyClassmember is deprecated. ' 717 'Please check the implementation of %s' % cls, 718 RemovedInSphinx40Warning, stacklevel=2) 719 break 720 else: 721 warnings.warn('PyClassmember is deprecated', 722 RemovedInSphinx40Warning, stacklevel=2) 723 724 return super().run() 725 726 def needs_arglist(self) -> bool: 727 return self.objtype.endswith('method') 728 729 def get_signature_prefix(self, sig: str) -> str: 730 if self.objtype == 'staticmethod': 731 return 'static ' 732 elif self.objtype == 'classmethod': 733 return 'classmethod ' 734 return '' 735 736 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 737 name, cls = name_cls 738 add_modules = self.env.config.add_module_names 739 if self.objtype == 'method': 740 try: 741 clsname, methname = name.rsplit('.', 1) 742 except ValueError: 743 if modname: 744 return _('%s() (in module %s)') % (name, modname) 745 else: 746 return '%s()' % name 747 if modname and add_modules: 748 return _('%s() (%s.%s method)') % (methname, modname, clsname) 749 else: 750 return _('%s() (%s method)') % (methname, clsname) 751 elif self.objtype == 'staticmethod': 752 try: 753 clsname, methname = name.rsplit('.', 1) 754 except ValueError: 755 if modname: 756 return _('%s() (in module %s)') % (name, modname) 757 else: 758 return '%s()' % name 759 if modname and add_modules: 760 return _('%s() (%s.%s static method)') % (methname, modname, 761 clsname) 762 else: 763 return _('%s() (%s static method)') % (methname, clsname) 764 elif self.objtype == 'classmethod': 765 try: 766 clsname, methname = name.rsplit('.', 1) 767 except ValueError: 768 if modname: 769 return _('%s() (in module %s)') % (name, modname) 770 else: 771 return '%s()' % name 772 if modname: 773 return _('%s() (%s.%s class method)') % (methname, modname, 774 clsname) 775 else: 776 return _('%s() (%s class method)') % (methname, clsname) 777 elif self.objtype == 'attribute': 778 try: 779 clsname, attrname = name.rsplit('.', 1) 780 except ValueError: 781 if modname: 782 return _('%s (in module %s)') % (name, modname) 783 else: 784 return name 785 if modname and add_modules: 786 return _('%s (%s.%s attribute)') % (attrname, modname, clsname) 787 else: 788 return _('%s (%s attribute)') % (attrname, clsname) 789 else: 790 return '' 791 792 793class PyMethod(PyObject): 794 """Description of a method.""" 795 796 option_spec = PyObject.option_spec.copy() 797 option_spec.update({ 798 'abstractmethod': directives.flag, 799 'async': directives.flag, 800 'classmethod': directives.flag, 801 'final': directives.flag, 802 'property': directives.flag, 803 'staticmethod': directives.flag, 804 }) 805 806 def needs_arglist(self) -> bool: 807 if 'property' in self.options: 808 return False 809 else: 810 return True 811 812 def get_signature_prefix(self, sig: str) -> str: 813 prefix = [] 814 if 'final' in self.options: 815 prefix.append('final') 816 if 'abstractmethod' in self.options: 817 prefix.append('abstract') 818 if 'async' in self.options: 819 prefix.append('async') 820 if 'classmethod' in self.options: 821 prefix.append('classmethod') 822 if 'property' in self.options: 823 prefix.append('property') 824 if 'staticmethod' in self.options: 825 prefix.append('static') 826 827 if prefix: 828 return ' '.join(prefix) + ' ' 829 else: 830 return '' 831 832 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 833 name, cls = name_cls 834 try: 835 clsname, methname = name.rsplit('.', 1) 836 if modname and self.env.config.add_module_names: 837 clsname = '.'.join([modname, clsname]) 838 except ValueError: 839 if modname: 840 return _('%s() (in module %s)') % (name, modname) 841 else: 842 return '%s()' % name 843 844 if 'classmethod' in self.options: 845 return _('%s() (%s class method)') % (methname, clsname) 846 elif 'property' in self.options: 847 return _('%s() (%s property)') % (methname, clsname) 848 elif 'staticmethod' in self.options: 849 return _('%s() (%s static method)') % (methname, clsname) 850 else: 851 return _('%s() (%s method)') % (methname, clsname) 852 853 854class PyClassMethod(PyMethod): 855 """Description of a classmethod.""" 856 857 option_spec = PyObject.option_spec.copy() 858 859 def run(self) -> List[Node]: 860 self.name = 'py:method' 861 self.options['classmethod'] = True 862 863 return super().run() 864 865 866class PyStaticMethod(PyMethod): 867 """Description of a staticmethod.""" 868 869 option_spec = PyObject.option_spec.copy() 870 871 def run(self) -> List[Node]: 872 self.name = 'py:method' 873 self.options['staticmethod'] = True 874 875 return super().run() 876 877 878class PyDecoratorMethod(PyMethod): 879 """Description of a decoratormethod.""" 880 881 def run(self) -> List[Node]: 882 self.name = 'py:method' 883 return super().run() 884 885 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 886 ret = super().handle_signature(sig, signode) 887 signode.insert(0, addnodes.desc_addname('@', '@')) 888 return ret 889 890 def needs_arglist(self) -> bool: 891 return False 892 893 894class PyAttribute(PyObject): 895 """Description of an attribute.""" 896 897 option_spec = PyObject.option_spec.copy() 898 option_spec.update({ 899 'type': directives.unchanged, 900 'value': directives.unchanged, 901 }) 902 903 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 904 fullname, prefix = super().handle_signature(sig, signode) 905 906 typ = self.options.get('type') 907 if typ: 908 annotations = _parse_annotation(typ, self.env) 909 signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations) 910 911 value = self.options.get('value') 912 if value: 913 signode += addnodes.desc_annotation(value, ' = ' + value) 914 915 return fullname, prefix 916 917 def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: 918 name, cls = name_cls 919 try: 920 clsname, attrname = name.rsplit('.', 1) 921 if modname and self.env.config.add_module_names: 922 clsname = '.'.join([modname, clsname]) 923 except ValueError: 924 if modname: 925 return _('%s (in module %s)') % (name, modname) 926 else: 927 return name 928 929 return _('%s (%s attribute)') % (attrname, clsname) 930 931 932class PyDecoratorMixin: 933 """ 934 Mixin for decorator directives. 935 """ 936 def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: 937 for cls in self.__class__.__mro__: 938 if cls.__name__ != 'DirectiveAdapter': 939 warnings.warn('PyDecoratorMixin is deprecated. ' 940 'Please check the implementation of %s' % cls, 941 RemovedInSphinx50Warning, stacklevel=2) 942 break 943 else: 944 warnings.warn('PyDecoratorMixin is deprecated', 945 RemovedInSphinx50Warning, stacklevel=2) 946 947 ret = super().handle_signature(sig, signode) # type: ignore 948 signode.insert(0, addnodes.desc_addname('@', '@')) 949 return ret 950 951 def needs_arglist(self) -> bool: 952 return False 953 954 955class PyModule(SphinxDirective): 956 """ 957 Directive to mark description of a new module. 958 """ 959 960 has_content = False 961 required_arguments = 1 962 optional_arguments = 0 963 final_argument_whitespace = False 964 option_spec = { 965 'platform': lambda x: x, 966 'synopsis': lambda x: x, 967 'noindex': directives.flag, 968 'deprecated': directives.flag, 969 } 970 971 def run(self) -> List[Node]: 972 domain = cast(PythonDomain, self.env.get_domain('py')) 973 974 modname = self.arguments[0].strip() 975 noindex = 'noindex' in self.options 976 self.env.ref_context['py:module'] = modname 977 ret = [] # type: List[Node] 978 if not noindex: 979 # note module to the domain 980 node_id = make_id(self.env, self.state.document, 'module', modname) 981 target = nodes.target('', '', ids=[node_id], ismod=True) 982 self.set_source_info(target) 983 984 # Assign old styled node_id not to break old hyperlinks (if possible) 985 # Note: Will removed in Sphinx-5.0 (RemovedInSphinx50Warning) 986 old_node_id = self.make_old_id(modname) 987 if node_id != old_node_id and old_node_id not in self.state.document.ids: 988 target['ids'].append(old_node_id) 989 990 self.state.document.note_explicit_target(target) 991 992 domain.note_module(modname, 993 node_id, 994 self.options.get('synopsis', ''), 995 self.options.get('platform', ''), 996 'deprecated' in self.options) 997 domain.note_object(modname, 'module', node_id, location=target) 998 999 # the platform and synopsis aren't printed; in fact, they are only 1000 # used in the modindex currently 1001 ret.append(target) 1002 indextext = '%s; %s' % (pairindextypes['module'], modname) 1003 inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)]) 1004 ret.append(inode) 1005 return ret 1006 1007 def make_old_id(self, name: str) -> str: 1008 """Generate old styled node_id. 1009 1010 Old styled node_id is incompatible with docutils' node_id. 1011 It can contain dots and hyphens. 1012 1013 .. note:: Old styled node_id was mainly used until Sphinx-3.0. 1014 """ 1015 return 'module-%s' % name 1016 1017 1018class PyCurrentModule(SphinxDirective): 1019 """ 1020 This directive is just to tell Sphinx that we're documenting 1021 stuff in module foo, but links to module foo won't lead here. 1022 """ 1023 1024 has_content = False 1025 required_arguments = 1 1026 optional_arguments = 0 1027 final_argument_whitespace = False 1028 option_spec = {} # type: Dict 1029 1030 def run(self) -> List[Node]: 1031 modname = self.arguments[0].strip() 1032 if modname == 'None': 1033 self.env.ref_context.pop('py:module', None) 1034 else: 1035 self.env.ref_context['py:module'] = modname 1036 return [] 1037 1038 1039class PyXRefRole(XRefRole): 1040 def process_link(self, env: BuildEnvironment, refnode: Element, 1041 has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: 1042 refnode['py:module'] = env.ref_context.get('py:module') 1043 refnode['py:class'] = env.ref_context.get('py:class') 1044 if not has_explicit_title: 1045 title = title.lstrip('.') # only has a meaning for the target 1046 target = target.lstrip('~') # only has a meaning for the title 1047 # if the first character is a tilde, don't display the module/class 1048 # parts of the contents 1049 if title[0:1] == '~': 1050 title = title[1:] 1051 dot = title.rfind('.') 1052 if dot != -1: 1053 title = title[dot + 1:] 1054 # if the first character is a dot, search more specific namespaces first 1055 # else search builtins first 1056 if target[0:1] == '.': 1057 target = target[1:] 1058 refnode['refspecific'] = True 1059 return title, target 1060 1061 1062def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None: 1063 """Filter ``:meta:`` field from its docstring.""" 1064 if domain != 'py': 1065 return 1066 1067 for node in content: 1068 if isinstance(node, nodes.field_list): 1069 fields = cast(List[nodes.field], node) 1070 for field in fields: 1071 field_name = cast(nodes.field_body, field[0]).astext().strip() 1072 if field_name == 'meta' or field_name.startswith('meta '): 1073 node.remove(field) 1074 break 1075 1076 1077class PythonModuleIndex(Index): 1078 """ 1079 Index subclass to provide the Python module index. 1080 """ 1081 1082 name = 'modindex' 1083 localname = _('Python Module Index') 1084 shortname = _('modules') 1085 1086 def generate(self, docnames: Iterable[str] = None 1087 ) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: 1088 content = {} # type: Dict[str, List[IndexEntry]] 1089 # list of prefixes to ignore 1090 ignores = None # type: List[str] 1091 ignores = self.domain.env.config['modindex_common_prefix'] # type: ignore 1092 ignores = sorted(ignores, key=len, reverse=True) 1093 # list of all modules, sorted by module name 1094 modules = sorted(self.domain.data['modules'].items(), 1095 key=lambda x: x[0].lower()) 1096 # sort out collapsable modules 1097 prev_modname = '' 1098 num_toplevels = 0 1099 for modname, (docname, node_id, synopsis, platforms, deprecated) in modules: 1100 if docnames and docname not in docnames: 1101 continue 1102 1103 for ignore in ignores: 1104 if modname.startswith(ignore): 1105 modname = modname[len(ignore):] 1106 stripped = ignore 1107 break 1108 else: 1109 stripped = '' 1110 1111 # we stripped the whole module name? 1112 if not modname: 1113 modname, stripped = stripped, '' 1114 1115 entries = content.setdefault(modname[0].lower(), []) 1116 1117 package = modname.split('.')[0] 1118 if package != modname: 1119 # it's a submodule 1120 if prev_modname == package: 1121 # first submodule - make parent a group head 1122 if entries: 1123 last = entries[-1] 1124 entries[-1] = IndexEntry(last[0], 1, last[2], last[3], 1125 last[4], last[5], last[6]) 1126 elif not prev_modname.startswith(package): 1127 # submodule without parent in list, add dummy entry 1128 entries.append(IndexEntry(stripped + package, 1, '', '', '', '', '')) 1129 subtype = 2 1130 else: 1131 num_toplevels += 1 1132 subtype = 0 1133 1134 qualifier = _('Deprecated') if deprecated else '' 1135 entries.append(IndexEntry(stripped + modname, subtype, docname, 1136 node_id, platforms, qualifier, synopsis)) 1137 prev_modname = modname 1138 1139 # apply heuristics when to collapse modindex at page load: 1140 # only collapse if number of toplevel modules is larger than 1141 # number of submodules 1142 collapse = len(modules) - num_toplevels < num_toplevels 1143 1144 # sort by first letter 1145 sorted_content = sorted(content.items()) 1146 1147 return sorted_content, collapse 1148 1149 1150class PythonDomain(Domain): 1151 """Python language domain.""" 1152 name = 'py' 1153 label = 'Python' 1154 object_types = { 1155 'function': ObjType(_('function'), 'func', 'obj'), 1156 'data': ObjType(_('data'), 'data', 'obj'), 1157 'class': ObjType(_('class'), 'class', 'exc', 'obj'), 1158 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'), 1159 'method': ObjType(_('method'), 'meth', 'obj'), 1160 'classmethod': ObjType(_('class method'), 'meth', 'obj'), 1161 'staticmethod': ObjType(_('static method'), 'meth', 'obj'), 1162 'attribute': ObjType(_('attribute'), 'attr', 'obj'), 1163 'module': ObjType(_('module'), 'mod', 'obj'), 1164 } # type: Dict[str, ObjType] 1165 1166 directives = { 1167 'function': PyFunction, 1168 'data': PyVariable, 1169 'class': PyClasslike, 1170 'exception': PyClasslike, 1171 'method': PyMethod, 1172 'classmethod': PyClassMethod, 1173 'staticmethod': PyStaticMethod, 1174 'attribute': PyAttribute, 1175 'module': PyModule, 1176 'currentmodule': PyCurrentModule, 1177 'decorator': PyDecoratorFunction, 1178 'decoratormethod': PyDecoratorMethod, 1179 } 1180 roles = { 1181 'data': PyXRefRole(), 1182 'exc': PyXRefRole(), 1183 'func': PyXRefRole(fix_parens=True), 1184 'class': PyXRefRole(), 1185 'const': PyXRefRole(), 1186 'attr': PyXRefRole(), 1187 'meth': PyXRefRole(fix_parens=True), 1188 'mod': PyXRefRole(), 1189 'obj': PyXRefRole(), 1190 } 1191 initial_data = { 1192 'objects': {}, # fullname -> docname, objtype 1193 'modules': {}, # modname -> docname, synopsis, platform, deprecated 1194 } # type: Dict[str, Dict[str, Tuple[Any]]] 1195 indices = [ 1196 PythonModuleIndex, 1197 ] 1198 1199 @property 1200 def objects(self) -> Dict[str, ObjectEntry]: 1201 return self.data.setdefault('objects', {}) # fullname -> ObjectEntry 1202 1203 def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None: 1204 """Note a python object for cross reference. 1205 1206 .. versionadded:: 2.1 1207 """ 1208 if name in self.objects: 1209 other = self.objects[name] 1210 logger.warning(__('duplicate object description of %s, ' 1211 'other instance in %s, use :noindex: for one of them'), 1212 name, other.docname, location=location) 1213 self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype) 1214 1215 @property 1216 def modules(self) -> Dict[str, ModuleEntry]: 1217 return self.data.setdefault('modules', {}) # modname -> ModuleEntry 1218 1219 def note_module(self, name: str, node_id: str, synopsis: str, 1220 platform: str, deprecated: bool) -> None: 1221 """Note a python module for cross reference. 1222 1223 .. versionadded:: 2.1 1224 """ 1225 self.modules[name] = ModuleEntry(self.env.docname, node_id, 1226 synopsis, platform, deprecated) 1227 1228 def clear_doc(self, docname: str) -> None: 1229 for fullname, obj in list(self.objects.items()): 1230 if obj.docname == docname: 1231 del self.objects[fullname] 1232 for modname, mod in list(self.modules.items()): 1233 if mod.docname == docname: 1234 del self.modules[modname] 1235 1236 def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: 1237 # XXX check duplicates? 1238 for fullname, obj in otherdata['objects'].items(): 1239 if obj.docname in docnames: 1240 self.objects[fullname] = obj 1241 for modname, mod in otherdata['modules'].items(): 1242 if mod.docname in docnames: 1243 self.modules[modname] = mod 1244 1245 def find_obj(self, env: BuildEnvironment, modname: str, classname: str, 1246 name: str, type: str, searchmode: int = 0 1247 ) -> List[Tuple[str, ObjectEntry]]: 1248 """Find a Python object for "name", perhaps using the given module 1249 and/or classname. Returns a list of (name, object entry) tuples. 1250 """ 1251 # skip parens 1252 if name[-2:] == '()': 1253 name = name[:-2] 1254 1255 if not name: 1256 return [] 1257 1258 matches = [] # type: List[Tuple[str, ObjectEntry]] 1259 1260 newname = None 1261 if searchmode == 1: 1262 if type is None: 1263 objtypes = list(self.object_types) 1264 else: 1265 objtypes = self.objtypes_for_role(type) 1266 if objtypes is not None: 1267 if modname and classname: 1268 fullname = modname + '.' + classname + '.' + name 1269 if fullname in self.objects and self.objects[fullname].objtype in objtypes: 1270 newname = fullname 1271 if not newname: 1272 if modname and modname + '.' + name in self.objects and \ 1273 self.objects[modname + '.' + name].objtype in objtypes: 1274 newname = modname + '.' + name 1275 elif name in self.objects and self.objects[name].objtype in objtypes: 1276 newname = name 1277 else: 1278 # "fuzzy" searching mode 1279 searchname = '.' + name 1280 matches = [(oname, self.objects[oname]) for oname in self.objects 1281 if oname.endswith(searchname) and 1282 self.objects[oname].objtype in objtypes] 1283 else: 1284 # NOTE: searching for exact match, object type is not considered 1285 if name in self.objects: 1286 newname = name 1287 elif type == 'mod': 1288 # only exact matches allowed for modules 1289 return [] 1290 elif classname and classname + '.' + name in self.objects: 1291 newname = classname + '.' + name 1292 elif modname and modname + '.' + name in self.objects: 1293 newname = modname + '.' + name 1294 elif modname and classname and \ 1295 modname + '.' + classname + '.' + name in self.objects: 1296 newname = modname + '.' + classname + '.' + name 1297 if newname is not None: 1298 matches.append((newname, self.objects[newname])) 1299 return matches 1300 1301 def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, 1302 type: str, target: str, node: pending_xref, contnode: Element 1303 ) -> Element: 1304 modname = node.get('py:module') 1305 clsname = node.get('py:class') 1306 searchmode = 1 if node.hasattr('refspecific') else 0 1307 matches = self.find_obj(env, modname, clsname, target, 1308 type, searchmode) 1309 1310 if not matches and type == 'attr': 1311 # fallback to meth (for property) 1312 matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode) 1313 1314 if not matches: 1315 return None 1316 elif len(matches) > 1: 1317 logger.warning(__('more than one target found for cross-reference %r: %s'), 1318 target, ', '.join(match[0] for match in matches), 1319 type='ref', subtype='python', location=node) 1320 name, obj = matches[0] 1321 1322 if obj[2] == 'module': 1323 return self._make_module_refnode(builder, fromdocname, name, contnode) 1324 else: 1325 return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) 1326 1327 def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, 1328 target: str, node: pending_xref, contnode: Element 1329 ) -> List[Tuple[str, Element]]: 1330 modname = node.get('py:module') 1331 clsname = node.get('py:class') 1332 results = [] # type: List[Tuple[str, Element]] 1333 1334 # always search in "refspecific" mode with the :any: role 1335 matches = self.find_obj(env, modname, clsname, target, None, 1) 1336 for name, obj in matches: 1337 if obj[2] == 'module': 1338 results.append(('py:mod', 1339 self._make_module_refnode(builder, fromdocname, 1340 name, contnode))) 1341 else: 1342 results.append(('py:' + self.role_for_objtype(obj[2]), 1343 make_refnode(builder, fromdocname, obj[0], obj[1], 1344 contnode, name))) 1345 return results 1346 1347 def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, 1348 contnode: Node) -> Element: 1349 # get additional info for modules 1350 module = self.modules[name] 1351 title = name 1352 if module.synopsis: 1353 title += ': ' + module.synopsis 1354 if module.deprecated: 1355 title += _(' (deprecated)') 1356 if module.platform: 1357 title += ' (' + module.platform + ')' 1358 return make_refnode(builder, fromdocname, module.docname, module.node_id, 1359 contnode, title) 1360 1361 def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: 1362 for modname, mod in self.modules.items(): 1363 yield (modname, modname, 'module', mod.docname, mod.node_id, 0) 1364 for refname, obj in self.objects.items(): 1365 if obj.objtype != 'module': # modules are already handled 1366 yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) 1367 1368 def get_full_qualified_name(self, node: Element) -> str: 1369 modname = node.get('py:module') 1370 clsname = node.get('py:class') 1371 target = node.get('reftarget') 1372 if target is None: 1373 return None 1374 else: 1375 return '.'.join(filter(None, [modname, clsname, target])) 1376 1377 1378def builtin_resolver(app: Sphinx, env: BuildEnvironment, 1379 node: pending_xref, contnode: Element) -> Element: 1380 """Do not emit nitpicky warnings for built-in types.""" 1381 def istyping(s: str) -> bool: 1382 if s.startswith('typing.'): 1383 s = s.split('.', 1)[1] 1384 1385 return s in typing.__all__ # type: ignore 1386 1387 if node.get('refdomain') != 'py': 1388 return None 1389 elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': 1390 return contnode 1391 elif node.get('reftype') in ('class', 'exc'): 1392 reftarget = node.get('reftarget') 1393 if inspect.isclass(getattr(builtins, reftarget, None)): 1394 # built-in class 1395 return contnode 1396 elif istyping(reftarget): 1397 # typing class 1398 return contnode 1399 1400 return None 1401 1402 1403def setup(app: Sphinx) -> Dict[str, Any]: 1404 app.setup_extension('sphinx.directives') 1405 1406 app.add_domain(PythonDomain) 1407 app.connect('object-description-transform', filter_meta_fields) 1408 app.connect('missing-reference', builtin_resolver, priority=900) 1409 1410 return { 1411 'version': 'builtin', 1412 'env_version': 2, 1413 'parallel_read_safe': True, 1414 'parallel_write_safe': True, 1415 } 1416