Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sphinx-doc/sphinx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/sphinx
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx')
-rw-r--r--sphinx/application.py2
-rw-r--r--sphinx/builders/linkcheck.py31
-rw-r--r--sphinx/config.py2
-rw-r--r--sphinx/domains/c.py4
-rw-r--r--sphinx/domains/cpp.py144
-rw-r--r--sphinx/domains/std.py2
-rw-r--r--sphinx/ext/autodoc/__init__.py394
-rw-r--r--sphinx/ext/autosummary/generate.py8
-rw-r--r--sphinx/ext/mathjax.py2
-rw-r--r--sphinx/ext/napoleon/docstring.py2
-rw-r--r--sphinx/jinja2glue.py2
-rw-r--r--sphinx/pycode/__init__.py3
-rw-r--r--sphinx/pycode/parser.py34
-rw-r--r--sphinx/search/__init__.py10
-rw-r--r--sphinx/themes/agogo/static/agogo.css_t1
-rw-r--r--sphinx/themes/basic/static/basic.css_t2
-rw-r--r--sphinx/themes/haiku/static/haiku.css_t1
-rw-r--r--sphinx/themes/nature/static/nature.css_t6
-rw-r--r--sphinx/themes/pyramid/static/pyramid.css_t6
-rw-r--r--sphinx/themes/scrolls/static/scrolls.css_t2
-rw-r--r--sphinx/themes/sphinxdoc/static/sphinxdoc.css_t1
-rw-r--r--sphinx/themes/traditional/static/traditional.css_t1
-rw-r--r--sphinx/util/console.py3
-rw-r--r--sphinx/util/images.py2
-rw-r--r--sphinx/util/inspect.py52
-rw-r--r--sphinx/util/nodes.py3
-rw-r--r--sphinx/util/pycompat.py2
-rw-r--r--sphinx/util/template.py2
-rw-r--r--sphinx/util/typing.py8
-rw-r--r--sphinx/writers/latex.py2
30 files changed, 571 insertions, 163 deletions
diff --git a/sphinx/application.py b/sphinx/application.py
index bd23c86e7..d84a2c975 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -165,7 +165,7 @@ class Sphinx:
if path.exists(self.outdir) and not path.isdir(self.outdir):
raise ApplicationError(__('Output directory (%s) is not a directory') %
- self.srcdir)
+ self.outdir)
if self.srcdir == self.outdir:
raise ApplicationError(__('Source directory and destination '
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 9fe689ec9..dd5317087 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -16,7 +16,7 @@ import threading
from html.parser import HTMLParser
from os import path
from typing import Any, Dict, List, Set, Tuple
-from urllib.parse import unquote
+from urllib.parse import unquote, urlparse
from docutils import nodes
from docutils.nodes import Node
@@ -36,6 +36,11 @@ from sphinx.util.requests import is_ssl_error
logger = logging.getLogger(__name__)
+DEFAULT_REQUEST_HEADERS = {
+ 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
+}
+
+
class AnchorCheckParser(HTMLParser):
"""Specialized HTML parser that looks for a specific anchor."""
@@ -107,13 +112,25 @@ class CheckExternalLinksBuilder(Builder):
def check_thread(self) -> None:
kwargs = {
'allow_redirects': True,
- 'headers': {
- 'Accept': 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
- },
- }
+ } # type: Dict
if self.app.config.linkcheck_timeout:
kwargs['timeout'] = self.app.config.linkcheck_timeout
+ def get_request_headers() -> Dict:
+ url = urlparse(uri)
+ candidates = ["%s://%s" % (url.scheme, url.netloc),
+ "%s://%s/" % (url.scheme, url.netloc),
+ uri,
+ "*"]
+
+ for u in candidates:
+ if u in self.config.linkcheck_request_headers:
+ headers = dict(DEFAULT_REQUEST_HEADERS)
+ headers.update(self.config.linkcheck_request_headers[u])
+ return headers
+
+ return {}
+
def check_uri() -> Tuple[str, str, int]:
# split off anchor
if '#' in uri:
@@ -139,6 +156,9 @@ class CheckExternalLinksBuilder(Builder):
else:
auth_info = None
+ # update request headers for the URL
+ kwargs['headers'] = get_request_headers()
+
try:
if anchor and self.app.config.linkcheck_anchors:
# Read the whole document and see if #anchor exists
@@ -337,6 +357,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('linkcheck_ignore', [], None)
app.add_config_value('linkcheck_auth', [], None)
+ app.add_config_value('linkcheck_request_headers', {}, None)
app.add_config_value('linkcheck_retries', 1, None)
app.add_config_value('linkcheck_timeout', None, None, [int])
app.add_config_value('linkcheck_workers', 5, None)
diff --git a/sphinx/config.py b/sphinx/config.py
index 6e6c256c5..92c203dfd 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -131,7 +131,7 @@ class Config:
'rst_epilog': (None, 'env', [str]),
'rst_prolog': (None, 'env', [str]),
'trim_doctest_flags': (True, 'env', []),
- 'primary_domain': ('py', 'env', [NoneType]), # type: ignore
+ 'primary_domain': ('py', 'env', [NoneType]),
'needs_sphinx': (None, None, [str]),
'needs_extensions': ({}, None, []),
'manpages_url': (None, 'env', []),
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 164a87254..36a8f1f65 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -432,9 +432,9 @@ class ASTUnaryOpExpr(ASTExpression):
def _stringify(self, transform: StringifyTransform) -> str:
if self.op[0] in 'cn':
- return transform(self.op) + " " + transform(self.expr)
+ return self.op + " " + transform(self.expr)
else:
- return transform(self.op) + transform(self.expr)
+ return self.op + transform(self.expr)
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index b801f4030..1783db491 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -951,12 +951,12 @@ class ASTFoldExpr(ASTExpression):
if self.leftExpr:
res.append(transform(self.leftExpr))
res.append(' ')
- res.append(transform(self.op))
+ res.append(self.op)
res.append(' ')
res.append('...')
if self.rightExpr:
res.append(' ')
- res.append(transform(self.op))
+ res.append(self.op)
res.append(' ')
res.append(transform(self.rightExpr))
res.append(')')
@@ -1223,9 +1223,9 @@ class ASTUnaryOpExpr(ASTExpression):
def _stringify(self, transform: StringifyTransform) -> str:
if self.op[0] in 'cn':
- return transform(self.op) + " " + transform(self.expr)
+ return self.op + " " + transform(self.expr)
else:
- return transform(self.op) + transform(self.expr)
+ return self.op + transform(self.expr)
def get_id(self, version: int) -> str:
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
@@ -3538,6 +3538,8 @@ class ASTTemplateIntroduction(ASTBase):
signode += nodes.Text('}')
+################################################################################
+
class ASTTemplateDeclarationPrefix(ASTBase):
def __init__(self,
templates: List[Union[ASTTemplateParams,
@@ -3566,18 +3568,35 @@ class ASTTemplateDeclarationPrefix(ASTBase):
t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)
+class ASTRequiresClause(ASTBase):
+ def __init__(self, expr: ASTExpression) -> None:
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'requires ' + transform(self.expr)
+
+ def describe_signature(self, signode: addnodes.desc_signature_line, mode: str,
+ env: "BuildEnvironment", symbol: "Symbol") -> None:
+ signode += nodes.Text('requires ', 'requires ')
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
################################################################################
################################################################################
class ASTDeclaration(ASTBase):
def __init__(self, objectType: str, directiveType: str, visibility: str,
- templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any,
+ templatePrefix: ASTTemplateDeclarationPrefix,
+ requiresClause: ASTRequiresClause, declaration: Any,
+ trailingRequiresClause: ASTRequiresClause,
semicolon: bool = False) -> None:
self.objectType = objectType
self.directiveType = directiveType
self.visibility = visibility
self.templatePrefix = templatePrefix
+ self.requiresClause = requiresClause
self.declaration = declaration
+ self.trailingRequiresClause = trailingRequiresClause
self.semicolon = semicolon
self.symbol = None # type: Symbol
@@ -3585,13 +3604,14 @@ class ASTDeclaration(ASTBase):
self.enumeratorScopedSymbol = None # type: Symbol
def clone(self) -> "ASTDeclaration":
- if self.templatePrefix:
- templatePrefixClone = self.templatePrefix.clone()
- else:
- templatePrefixClone = None
- return ASTDeclaration(self.objectType, self.directiveType,
- self.visibility, templatePrefixClone,
- self.declaration.clone(), self.semicolon)
+ templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
+ requiresClasueClone = self.requiresClause.clone() if self.requiresClause else None
+ trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
+ if self.trailingRequiresClause else None
+ return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
+ templatePrefixClone, requiresClasueClone,
+ self.declaration.clone(), trailingRequiresClasueClone,
+ self.semicolon)
@property
def name(self) -> ASTNestedName:
@@ -3619,6 +3639,17 @@ class ASTDeclaration(ASTBase):
res = []
if self.templatePrefix:
res.append(self.templatePrefix.get_id(version))
+ if self.requiresClause or self.trailingRequiresClause:
+ if version < 4:
+ raise NoOldIdError()
+ res.append('IQ')
+ if self.requiresClause and self.trailingRequiresClause:
+ res.append('aa')
+ if self.requiresClause:
+ res.append(self.requiresClause.expr.get_id(version))
+ if self.trailingRequiresClause:
+ res.append(self.trailingRequiresClause.expr.get_id(version))
+ res.append('E')
res.append(self.declaration.get_id(version, self.objectType, self.symbol))
return ''.join(res)
@@ -3632,7 +3663,13 @@ class ASTDeclaration(ASTBase):
res.append(' ')
if self.templatePrefix:
res.append(transform(self.templatePrefix))
+ if self.requiresClause:
+ res.append(transform(self.requiresClause))
+ res.append(' ')
res.append(transform(self.declaration))
+ if self.trailingRequiresClause:
+ res.append(' ')
+ res.append(transform(self.trailingRequiresClause))
if self.semicolon:
res.append(';')
return ''.join(res)
@@ -3653,6 +3690,11 @@ class ASTDeclaration(ASTBase):
self.templatePrefix.describe_signature(signode, mode, env,
symbol=self.symbol,
lineSpec=options.get('tparam-line-spec'))
+ if self.requiresClause:
+ reqNode = addnodes.desc_signature_line()
+ reqNode.sphinx_line_type = 'requiresClause'
+ signode.append(reqNode)
+ self.requiresClause.describe_signature(reqNode, 'markType', env, self.symbol)
signode += mainDeclNode
if self.visibility and self.visibility != "public":
mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
@@ -3688,8 +3730,16 @@ class ASTDeclaration(ASTBase):
else:
assert False
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
+ lastDeclNode = mainDeclNode
+ if self.trailingRequiresClause:
+ trailingReqNode = addnodes.desc_signature_line()
+ trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
+ signode.append(trailingReqNode)
+ lastDeclNode = trailingReqNode
+ self.trailingRequiresClause.describe_signature(
+ trailingReqNode, 'markType', env, self.symbol)
if self.semicolon:
- mainDeclNode += nodes.Text(';')
+ lastDeclNode += nodes.Text(';')
class ASTNamespace(ASTBase):
@@ -3808,7 +3858,7 @@ class Symbol:
continue
# only add a declaration if we our self are from a declaration
if self.declaration:
- decl = ASTDeclaration('templateParam', None, None, None, tp)
+ decl = ASTDeclaration('templateParam', None, None, None, None, tp, None)
else:
decl = None
nne = ASTNestedNameElement(tp.get_identifier(), None)
@@ -3823,7 +3873,7 @@ class Symbol:
if nn is None:
continue
# (comparing to the template params: we have checked that we are a declaration)
- decl = ASTDeclaration('functionParam', None, None, None, fp)
+ decl = ASTDeclaration('functionParam', None, None, None, None, fp, None)
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, [], decl, self.docname)
@@ -6297,8 +6347,61 @@ class DefinitionParser(BaseParser):
'Expected ",", or "}".')
return ASTTemplateIntroduction(concept, params)
+ def _parse_requires_clause(self) -> Optional[ASTRequiresClause]:
+ # requires-clause -> 'requires' constraint-logical-or-expression
+ # constraint-logical-or-expression
+ # -> constraint-logical-and-expression
+ # | constraint-logical-or-expression '||' constraint-logical-and-expression
+ # constraint-logical-and-expression
+ # -> primary-expression
+ # | constraint-logical-and-expression '&&' primary-expression
+ self.skip_ws()
+ if not self.skip_word('requires'):
+ return None
+
+ def parse_and_expr(self: DefinitionParser) -> ASTExpression:
+ andExprs = []
+ ops = []
+ andExprs.append(self._parse_primary_expression())
+ while True:
+ self.skip_ws()
+ oneMore = False
+ if self.skip_string('&&'):
+ oneMore = True
+ ops.append('&&')
+ elif self.skip_word('and'):
+ oneMore = True
+ ops.append('and')
+ if not oneMore:
+ break
+ andExprs.append(self._parse_primary_expression())
+ if len(andExprs) == 1:
+ return andExprs[0]
+ else:
+ return ASTBinOpExpr(andExprs, ops)
+
+ orExprs = []
+ ops = []
+ orExprs.append(parse_and_expr(self))
+ while True:
+ self.skip_ws()
+ oneMore = False
+ if self.skip_string('||'):
+ oneMore = True
+ ops.append('||')
+ elif self.skip_word('or'):
+ oneMore = True
+ ops.append('or')
+ if not oneMore:
+ break
+ orExprs.append(parse_and_expr(self))
+ if len(orExprs) == 1:
+ return ASTRequiresClause(orExprs[0])
+ else:
+ return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
+
def _parse_template_declaration_prefix(self, objectType: str
- ) -> ASTTemplateDeclarationPrefix:
+ ) -> Optional[ASTTemplateDeclarationPrefix]:
templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]]
while 1:
self.skip_ws()
@@ -6377,6 +6480,8 @@ class DefinitionParser(BaseParser):
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
visibility = None
templatePrefix = None
+ requiresClause = None
+ trailingRequiresClause = None
declaration = None # type: Any
self.skip_ws()
@@ -6385,6 +6490,8 @@ class DefinitionParser(BaseParser):
if objectType in ('type', 'concept', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix(objectType)
+ if objectType == 'function' and templatePrefix is not None:
+ requiresClause = self._parse_requires_clause()
if objectType == 'type':
prevErrors = []
@@ -6410,6 +6517,8 @@ class DefinitionParser(BaseParser):
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
declaration = self._parse_type(named=True, outer='function')
+ if templatePrefix is not None:
+ trailingRequiresClause = self._parse_requires_clause()
elif objectType == 'class':
declaration = self._parse_class()
elif objectType == 'union':
@@ -6427,7 +6536,8 @@ class DefinitionParser(BaseParser):
self.skip_ws()
semicolon = self.skip_string(';')
return ASTDeclaration(objectType, directiveType, visibility,
- templatePrefix, declaration, semicolon)
+ templatePrefix, requiresClause, declaration,
+ trailingRequiresClause, semicolon)
def parse_namespace_object(self) -> ASTNamespace:
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 6dc597022..fbbed3a6b 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -43,7 +43,7 @@ logger = logging.getLogger(__name__)
# RE for option descriptions
-option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
+option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=[]+)(=?\s*.*)')
# RE for grammar tokens
token_re = re.compile(r'`(\w+)`', re.U)
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index f9c775f7d..31301e01e 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -13,7 +13,7 @@
import importlib
import re
import warnings
-from inspect import Parameter
+from inspect import Parameter, Signature
from types import ModuleType
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
@@ -392,6 +392,17 @@ class Documenter:
# directives of course)
return '.'.join(self.objpath) or self.modname
+ def _call_format_args(self, **kwargs: Any) -> str:
+ if kwargs:
+ try:
+ return self.format_args(**kwargs)
+ except TypeError:
+ # avoid chaining exceptions, by putting nothing here
+ pass
+
+ # retry without arguments for old documenters
+ return self.format_args()
+
def format_signature(self, **kwargs: Any) -> str:
"""Format the signature (arguments and return annotation) of the object.
@@ -405,12 +416,7 @@ class Documenter:
# try to introspect the signature
try:
retann = None
- try:
- args = self.format_args(**kwargs)
- except TypeError:
- # retry without arguments for old documenters
- args = self.format_args()
-
+ args = self._call_format_args(**kwargs)
if args:
matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args)
if matched:
@@ -714,12 +720,26 @@ class Documenter:
'.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr))
- member_order = self.options.member_order or \
- self.env.config.autodoc_member_order
- if member_order == 'groupwise':
+
+ member_order = self.options.member_order or self.env.config.autodoc_member_order
+ memberdocumenters = self.sort_members(memberdocumenters, member_order)
+
+ for documenter, isattr in memberdocumenters:
+ documenter.generate(
+ all_members=True, real_modname=self.real_modname,
+ check_module=members_check_module and not isattr)
+
+ # reset current objects
+ self.env.temp_data['autodoc:module'] = None
+ self.env.temp_data['autodoc:class'] = None
+
+ def sort_members(self, documenters: List[Tuple["Documenter", bool]],
+ order: str) -> List[Tuple["Documenter", bool]]:
+ """Sort the given member list."""
+ if order == 'groupwise':
# sort by group; alphabetically within groups
- memberdocumenters.sort(key=lambda e: (e[0].member_order, e[0].name))
- elif member_order == 'bysource':
+ documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
+ elif order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
@@ -727,25 +747,16 @@ class Documenter:
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
- memberdocumenters.sort(key=keyfunc)
+ documenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
- elif member_order == 'alphabetical':
- memberdocumenters.sort(key=lambda e: e[0].name)
- else:
- raise ValueError("Illegal member order {}".format(member_order))
+ else: # alphabetical
+ documenters.sort(key=lambda e: e[0].name)
- for documenter, isattr in memberdocumenters:
- documenter.generate(
- all_members=True, real_modname=self.real_modname,
- check_module=members_check_module and not isattr)
-
- # reset current objects
- self.env.temp_data['autodoc:module'] = None
- self.env.temp_data['autodoc:class'] = None
+ return documenters
def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
@@ -852,6 +863,7 @@ class ModuleDocumenter(Documenter):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
+ self.__all__ = None
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@@ -874,6 +886,30 @@ class ModuleDocumenter(Documenter):
type='autodoc')
return ret
+ def import_object(self) -> Any:
+ def is_valid_module_all(__all__: Any) -> bool:
+ """Check the given *__all__* is valid for a module."""
+ if (isinstance(__all__, (list, tuple)) and
+ all(isinstance(e, str) for e in __all__)):
+ return True
+ else:
+ return False
+
+ ret = super().import_object()
+
+ if not self.options.ignore_module_all:
+ __all__ = getattr(self.object, '__all__', None)
+ if is_valid_module_all(__all__):
+ # valid __all__ found. copy it to self.__all__
+ self.__all__ = __all__
+ elif __all__:
+ # invalid __all__ found.
+ logger.warning(__('__all__ should be a list of strings, not %r '
+ '(in module %s) -- ignoring __all__') %
+ (__all__, self.fullname), type='autodoc')
+
+ return ret
+
def add_directive_header(self, sig: str) -> None:
Documenter.add_directive_header(self, sig)
@@ -889,24 +925,12 @@ class ModuleDocumenter(Documenter):
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
if want_all:
- if (self.options.ignore_module_all or not
- hasattr(self.object, '__all__')):
+ if self.__all__:
+ memberlist = self.__all__
+ else:
# for implicit module members, check __module__ to avoid
# documenting imported objects
return True, get_module_members(self.object)
- else:
- memberlist = self.object.__all__
- # Sometimes __all__ is broken...
- if not isinstance(memberlist, (list, tuple)) or not \
- all(isinstance(entry, str) for entry in memberlist):
- logger.warning(
- __('__all__ should be a list of strings, not %r '
- '(in module %s) -- ignoring __all__') %
- (memberlist, self.fullname),
- type='autodoc'
- )
- # fall back to all members
- return True, get_module_members(self.object)
else:
memberlist = self.options.members or []
ret = []
@@ -922,6 +946,25 @@ class ModuleDocumenter(Documenter):
)
return False, ret
+ def sort_members(self, documenters: List[Tuple["Documenter", bool]],
+ order: str) -> List[Tuple["Documenter", bool]]:
+ if order == 'bysource' and self.__all__:
+ # Sort alphabetically first (for members not listed on the __all__)
+ documenters.sort(key=lambda e: e[0].name)
+
+ # Sort by __all__
+ def keyfunc(entry: Tuple[Documenter, bool]) -> int:
+ name = entry[0].name.split('::')[1]
+ if name in self.__all__:
+ return self.__all__.index(name)
+ else:
+ return len(self.__all__)
+ documenters.sort(key=keyfunc)
+
+ return documenters
+ else:
+ return super().sort_members(documenters, order)
+
class ModuleLevelDocumenter(Documenter):
"""
@@ -993,39 +1036,71 @@ class DocstringSignatureMixin:
Mixin for FunctionDocumenter and MethodDocumenter to provide the
feature of reading the signature from the docstring.
"""
+ _new_docstrings = None # type: List[List[str]]
+ _signatures = None # type: List[str]
def _find_signature(self, encoding: str = None) -> Tuple[str, str]:
if encoding is not None:
warnings.warn("The 'encoding' argument to autodoc.%s._find_signature() is "
"deprecated." % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
+
+ # candidates of the object name
+ valid_names = [self.objpath[-1]] # type: ignore
+ if isinstance(self, ClassDocumenter):
+ valid_names.append('__init__')
+ if hasattr(self.object, '__mro__'):
+ valid_names.extend(cls.__name__ for cls in self.object.__mro__)
+
docstrings = self.get_doc()
self._new_docstrings = docstrings[:]
+ self._signatures = []
result = None
for i, doclines in enumerate(docstrings):
- # no lines in docstring, no match
- if not doclines:
- continue
- # match first line of docstring against signature RE
- match = py_ext_sig_re.match(doclines[0])
- if not match:
- continue
- exmod, path, base, args, retann = match.groups()
- # the base name must match ours
- valid_names = [self.objpath[-1]] # type: ignore
- if isinstance(self, ClassDocumenter):
- valid_names.append('__init__')
- if hasattr(self.object, '__mro__'):
- valid_names.extend(cls.__name__ for cls in self.object.__mro__)
- if base not in valid_names:
- continue
- # re-prepare docstring to ignore more leading indentation
- tab_width = self.directive.state.document.settings.tab_width # type: ignore
- self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:]),
- tabsize=tab_width)
- result = args, retann
- # don't look any further
- break
+ for j, line in enumerate(doclines):
+ if not line:
+ # no lines in docstring, no match
+ break
+
+ if line.endswith('\\'):
+ multiline = True
+ line = line.rstrip('\\').rstrip()
+ else:
+ multiline = False
+
+ # match first line of docstring against signature RE
+ match = py_ext_sig_re.match(line)
+ if not match:
+ continue
+ exmod, path, base, args, retann = match.groups()
+
+ # the base name must match ours
+ if base not in valid_names:
+ continue
+
+ # re-prepare docstring to ignore more leading indentation
+ tab_width = self.directive.state.document.settings.tab_width # type: ignore
+ self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]),
+ tabsize=tab_width)
+
+ if result is None:
+ # first signature
+ result = args, retann
+ else:
+ # subsequent signatures
+ self._signatures.append("(%s) -> %s" % (args, retann))
+
+ if multiline:
+ # the signature have multiple signatures on docstring
+ continue
+ else:
+ # don't look any further
+ break
+
+ if result:
+ # finish the loop when signature found
+ break
+
return result
def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
@@ -1033,9 +1108,8 @@ class DocstringSignatureMixin:
warnings.warn("The 'encoding' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
- lines = getattr(self, '_new_docstrings', None)
- if lines is not None:
- return lines
+ if self._new_docstrings is not None:
+ return self._new_docstrings
return super().get_doc(None, ignore) # type: ignore
def format_signature(self, **kwargs: Any) -> str:
@@ -1045,7 +1119,11 @@ class DocstringSignatureMixin:
result = self._find_signature()
if result is not None:
self.args, self.retann = result
- return super().format_signature(**kwargs) # type: ignore
+ sig = super().format_signature(**kwargs) # type: ignore
+ if self._signatures:
+ return "\n".join([sig] + self._signatures)
+ else:
+ return sig
class DocstringStripSignatureMixin(DocstringSignatureMixin):
@@ -1114,8 +1192,14 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
self.add_line(' :async:', sourcename)
def format_signature(self, **kwargs: Any) -> str:
- sig = super().format_signature(**kwargs)
- sigs = [sig]
+ sigs = []
+ if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
+ # Use signatures for overloaded functions instead of the implementation function.
+ overloaded = True
+ else:
+ overloaded = False
+ sig = super().format_signature(**kwargs)
+ sigs.append(sig)
if inspect.is_singledispatch_function(self.object):
# append signature of singledispatch'ed functions
@@ -1127,7 +1211,12 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
documenter = FunctionDocumenter(self.directive, '')
documenter.object = func
+ documenter.objpath = [None]
sigs.append(documenter.format_signature())
+ if overloaded:
+ for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
+ sig = stringify_signature(overload, **kwargs)
+ sigs.append(sig)
return "\n".join(sigs)
@@ -1168,6 +1257,14 @@ class DecoratorDocumenter(FunctionDocumenter):
return None
+# Types which have confusing metaclass signatures it would be best not to show.
+# These are listed by name, rather than storing the objects themselves, to avoid
+# needing to import the modules.
+_METACLASS_CALL_BLACKLIST = [
+ 'enum.EnumMeta.__call__',
+]
+
+
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for classes.
@@ -1182,6 +1279,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
'private-members': bool_option, 'special-members': members_option,
} # type: Dict[str, Callable]
+ _signature_class = None # type: Any
+ _signature_method_name = None # type: str
+
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
@@ -1202,32 +1302,115 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
+ def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]:
+ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
+ """ Get the `attr` function or method from `obj`, if it is user-defined. """
+ if inspect.is_builtin_class_method(obj, attr):
+ return None
+ attr = self.get_attr(obj, attr, None)
+ if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
+ return None
+ return attr
+
+ # This sequence is copied from inspect._signature_from_callable.
+ # ValueError means that no signature could be found, so we keep going.
+
+ # First, let's see if it has an overloaded __call__ defined
+ # in its metaclass
+ call = get_user_defined_function_or_method(type(self.object), '__call__')
+
+ if call is not None:
+ if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST:
+ call = None
+
+ if call is not None:
+ self.env.app.emit('autodoc-before-process-signature', call, True)
+ try:
+ sig = inspect.signature(call, bound_method=True)
+ return type(self.object), '__call__', sig
+ except ValueError:
+ pass
+
+ # Now we check if the 'obj' class has a '__new__' method
+ new = get_user_defined_function_or_method(self.object, '__new__')
+ if new is not None:
+ self.env.app.emit('autodoc-before-process-signature', new, True)
+ try:
+ sig = inspect.signature(new, bound_method=True)
+ return self.object, '__new__', sig
+ except ValueError:
+ pass
+
+ # Finally, we should have at least __init__ implemented
+ init = get_user_defined_function_or_method(self.object, '__init__')
+ if init is not None:
+ self.env.app.emit('autodoc-before-process-signature', init, True)
+ try:
+ sig = inspect.signature(init, bound_method=True)
+ return self.object, '__init__', sig
+ except ValueError:
+ pass
+
+ # None of the attributes are user-defined, so fall back to let inspect
+ # handle it.
+ # We don't know the exact method that inspect.signature will read
+ # the signature from, so just pass the object itself to our hook.
+ self.env.app.emit('autodoc-before-process-signature', self.object, False)
+ try:
+ sig = inspect.signature(self.object, bound_method=False)
+ return None, None, sig
+ except ValueError:
+ pass
+
+ # Still no signature: happens e.g. for old-style classes
+ # with __init__ in C and no `__text_signature__`.
+ return None, None, None
+
def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
- # for classes, the relevant signature is the __init__ method's
- initmeth = self.get_attr(self.object, '__init__', None)
- # classes without __init__ method, default __init__ or
- # __init__ written in C?
- if initmeth is None or \
- inspect.is_builtin_class_method(self.object, '__init__') or \
- not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
- return None
try:
- self.env.app.emit('autodoc-before-process-signature', initmeth, True)
- sig = inspect.signature(initmeth, bound_method=True)
- return stringify_signature(sig, show_return_annotation=False, **kwargs)
- except TypeError:
- # still not possible: happens e.g. for old-style classes
- # with __init__ in C
+ self._signature_class, self._signature_method_name, sig = self._get_signature()
+ except TypeError as exc:
+ # __signature__ attribute contained junk
+ logger.warning(__("Failed to get a constructor signature for %s: %s"),
+ self.fullname, exc)
return None
+ if sig is None:
+ return None
+
+ return stringify_signature(sig, show_return_annotation=False, **kwargs)
+
def format_signature(self, **kwargs: Any) -> str:
if self.doc_as_attr:
return ''
- return super().format_signature(**kwargs)
+ sig = super().format_signature()
+
+ overloaded = False
+ qualname = None
+ # TODO: recreate analyzer for the module of class (To be clear, owner of the method)
+ if self._signature_class and self._signature_method_name and self.analyzer:
+ qualname = '.'.join([self._signature_class.__qualname__,
+ self._signature_method_name])
+ if qualname in self.analyzer.overloads:
+ overloaded = True
+
+ sigs = []
+ if overloaded:
+ # Use signatures for overloaded methods instead of the implementation method.
+ for overload in self.analyzer.overloads.get(qualname):
+ parameters = list(overload.parameters.values())
+ overload = overload.replace(parameters=parameters[1:],
+ return_annotation=Parameter.empty)
+ sig = stringify_signature(overload, **kwargs)
+ sigs.append(sig)
+ else:
+ sigs.append(sig)
+
+ return "\n".join(sigs)
def add_directive_header(self, sig: str) -> None:
sourcename = self.get_sourcename()
@@ -1437,6 +1620,30 @@ class DataDeclarationDocumenter(DataDocumenter):
super().add_content(more_content, no_docstring=True)
+class GenericAliasDocumenter(DataDocumenter):
+ """
+ Specialized Documenter subclass for GenericAliases.
+ """
+
+ objtype = 'genericalias'
+ directivetype = 'data'
+ priority = DataDocumenter.priority + 1
+
+ @classmethod
+ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
+ ) -> bool:
+ return inspect.isgenericalias(member)
+
+ def add_directive_header(self, sig: str) -> None:
+ self.options.annotation = SUPPRESS # type: ignore
+ super().add_directive_header(sig)
+
+ def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
+ name = stringify_typehint(self.object)
+ content = StringList([_('alias of %s') % name], source='')
+ super().add_content(content)
+
+
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for methods (normal, static and class).
@@ -1526,8 +1733,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
pass
def format_signature(self, **kwargs: Any) -> str:
- sig = super().format_signature(**kwargs)
- sigs = [sig]
+ sigs = []
+ if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads:
+ # Use signatures for overloaded methods instead of the implementation method.
+ overloaded = True
+ else:
+ overloaded = False
+ sig = super().format_signature(**kwargs)
+ sigs.append(sig)
meth = self.parent.__dict__.get(self.objpath[-1])
if inspect.is_singledispatch_method(meth):
@@ -1543,6 +1756,14 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
documenter.object = func
documenter.objpath = [None]
sigs.append(documenter.format_signature())
+ if overloaded:
+ for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
+ if not inspect.isstaticmethod(self.object, cls=self.parent,
+ name=self.object_name):
+ parameters = list(overload.parameters.values())
+ overload = overload.replace(parameters=parameters[1:])
+ sig = stringify_signature(overload, **kwargs)
+ sigs.append(sig)
return "\n".join(sigs)
@@ -1795,6 +2016,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter)
app.add_autodocumenter(DataDeclarationDocumenter)
+ app.add_autodocumenter(GenericAliasDocumenter)
app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(DecoratorDocumenter)
app.add_autodocumenter(MethodDocumenter)
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 473ac3db6..fe7daf214 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -91,14 +91,14 @@ def setup_documenters(app: Any) -> None:
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
- SlotsAttributeDocumenter, DataDeclarationDocumenter,
+ SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
SingledispatchFunctionDocumenter,
)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
- SlotsAttributeDocumenter, DataDeclarationDocumenter,
+ SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]
for documenter in documenters:
@@ -143,11 +143,11 @@ class AutosummaryRenderer:
if isinstance(app, (Sphinx, DummyApplication)):
if app.translator:
self.env.add_extension("jinja2.ext.i18n")
- self.env.install_gettext_translations(app.translator) # type: ignore
+ self.env.install_gettext_translations(app.translator)
elif isinstance(app, Builder):
if app.app.translator:
self.env.add_extension("jinja2.ext.i18n")
- self.env.install_gettext_translations(app.app.translator) # type: ignore
+ self.env.install_gettext_translations(app.app.translator)
def exists(self, template_name: str) -> bool:
"""Check if template file exists."""
diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py
index e13dcbeb7..cc3cd4ba3 100644
--- a/sphinx/ext/mathjax.py
+++ b/sphinx/ext/mathjax.py
@@ -96,7 +96,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
# more information for mathjax secure url is here:
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
app.add_config_value('mathjax_path',
- 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
+ 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
'config=TeX-AMS-MML_HTMLorMML', 'html')
app.add_config_value('mathjax_options', {}, 'html')
app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 11409e6f6..32edd7f8f 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -318,7 +318,7 @@ class GoogleDocstring:
return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name: str) -> str:
- if name.endswith('_'):
+ if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False):
name = name[:-1] + r'\_'
if name[:2] == '**':
diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py
index e943cfb1e..52d0257e5 100644
--- a/sphinx/jinja2glue.py
+++ b/sphinx/jinja2glue.py
@@ -186,7 +186,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.environment.globals['accesskey'] = contextfunction(accesskey)
self.environment.globals['idgen'] = idgen
if use_i18n:
- self.environment.install_gettext_translations(builder.app.translator) # type: ignore # NOQA
+ self.environment.install_gettext_translations(builder.app.translator)
def render(self, template: str, context: Dict) -> str: # type: ignore
return self.environment.get_template(template).render(context)
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index 4879fb349..963680a54 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -12,6 +12,7 @@ import re
import tokenize
import warnings
from importlib import import_module
+from inspect import Signature
from io import StringIO
from os import path
from typing import Any, Dict, IO, List, Tuple, Optional
@@ -145,6 +146,7 @@ class ModuleAnalyzer:
self.annotations = None # type: Dict[Tuple[str, str], str]
self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
self.finals = None # type: List[str]
+ self.overloads = None # type: Dict[str, List[Signature]]
self.tagorder = None # type: Dict[str, int]
self.tags = None # type: Dict[str, Tuple[str, int, int]]
@@ -163,6 +165,7 @@ class ModuleAnalyzer:
self.annotations = parser.annotations
self.finals = parser.finals
+ self.overloads = parser.overloads
self.tags = parser.definitions
self.tagorder = parser.deforders
except Exception as exc:
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index 3762c72cc..ec89a3616 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -12,12 +12,14 @@ import itertools
import re
import sys
import tokenize
+from inspect import Signature
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
from tokenize import COMMENT, NL
from typing import Any, Dict, List, Optional, Tuple
from sphinx.pycode.ast import ast # for py37 or older
from sphinx.pycode.ast import parse, unparse
+from sphinx.util.inspect import signature_from_ast
comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$')
@@ -232,8 +234,10 @@ class VariableCommentPicker(ast.NodeVisitor):
self.previous = None # type: ast.AST
self.deforders = {} # type: Dict[str, int]
self.finals = [] # type: List[str]
+ self.overloads = {} # type: Dict[str, List[Signature]]
self.typing = None # type: str
self.typing_final = None # type: str
+ self.typing_overload = None # type: str
super().__init__()
def get_qualname_for(self, name: str) -> Optional[List[str]]:
@@ -257,6 +261,12 @@ class VariableCommentPicker(ast.NodeVisitor):
if qualname:
self.finals.append(".".join(qualname))
+ def add_overload_entry(self, func: ast.FunctionDef) -> None:
+ qualname = self.get_qualname_for(func.name)
+ if qualname:
+ overloads = self.overloads.setdefault(".".join(qualname), [])
+ overloads.append(signature_from_ast(func))
+
def add_variable_comment(self, name: str, comment: str) -> None:
qualname = self.get_qualname_for(name)
if qualname:
@@ -285,6 +295,22 @@ class VariableCommentPicker(ast.NodeVisitor):
return False
+ def is_overload(self, decorators: List[ast.expr]) -> bool:
+ overload = []
+ if self.typing:
+ overload.append('%s.overload' % self.typing)
+ if self.typing_overload:
+ overload.append(self.typing_overload)
+
+ for decorator in decorators:
+ try:
+ if unparse(decorator) in overload:
+ return True
+ except NotImplementedError:
+ pass
+
+ return False
+
def get_self(self) -> ast.arg:
"""Returns the name of first argument if in function."""
if self.current_function and self.current_function.args.args:
@@ -310,6 +336,8 @@ class VariableCommentPicker(ast.NodeVisitor):
self.typing = name.asname or name.name
elif name.name == 'typing.final':
self.typing_final = name.asname or name.name
+ elif name.name == 'typing.overload':
+ self.typing_overload = name.asname or name.name
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""Handles Import node and record it to definition orders."""
@@ -318,6 +346,8 @@ class VariableCommentPicker(ast.NodeVisitor):
if node.module == 'typing' and name.name == 'final':
self.typing_final = name.asname or name.name
+ elif node.module == 'typing' and name.name == 'overload':
+ self.typing_overload = name.asname or name.name
def visit_Assign(self, node: ast.Assign) -> None:
"""Handles Assign node and pick up a variable comment."""
@@ -417,6 +447,8 @@ class VariableCommentPicker(ast.NodeVisitor):
self.add_entry(node.name) # should be called before setting self.current_function
if self.is_final(node.decorator_list):
self.add_final_entry(node.name)
+ if self.is_overload(node.decorator_list):
+ self.add_overload_entry(node)
self.context.append(node.name)
self.current_function = node
for child in node.body:
@@ -518,6 +550,7 @@ class Parser:
self.deforders = {} # type: Dict[str, int]
self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
self.finals = [] # type: List[str]
+ self.overloads = {} # type: Dict[str, List[Signature]]
def parse(self) -> None:
"""Parse the source code."""
@@ -533,6 +566,7 @@ class Parser:
self.comments = picker.comments
self.deforders = picker.deforders
self.finals = picker.finals
+ self.overloads = picker.overloads
def parse_definition(self) -> None:
"""Parse the location of definitions from the code."""
diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py
index 74fbaade9..b531145f4 100644
--- a/sphinx/search/__init__.py
+++ b/sphinx/search/__init__.py
@@ -297,8 +297,8 @@ class IndexBuilder:
frozen.get('envversion') != self.env.version:
raise ValueError('old format')
index2fn = frozen['docnames']
- self._filenames = dict(zip(index2fn, frozen['filenames']))
- self._titles = dict(zip(index2fn, frozen['titles']))
+ self._filenames = dict(zip(index2fn, frozen['filenames'])) # type: ignore
+ self._titles = dict(zip(index2fn, frozen['titles'])) # type: ignore
def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]:
rv = {}
@@ -359,13 +359,13 @@ class IndexBuilder:
def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]]
for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)):
- for k, v in mapping.items():
+ for k, v in mapping.items(): # type: ignore
if len(v) == 1:
fn, = v
if fn in fn2index:
- rv[k] = fn2index[fn]
+ rv[k] = fn2index[fn] # type: ignore
else:
- rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index])
+ rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) # type: ignore # NOQA
return rvs
def freeze(self) -> Dict[str, Any]:
diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t
index d74604ac1..ff43186da 100644
--- a/sphinx/themes/agogo/static/agogo.css_t
+++ b/sphinx/themes/agogo/static/agogo.css_t
@@ -207,7 +207,6 @@ div.document .section:first-child {
div.document div.highlight {
padding: 3px;
- background-color: #eeeeec;
border-top: 2px solid #dddddd;
border-bottom: 2px solid #dddddd;
margin-top: .8em;
diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t
index 45908ece1..a7c58569f 100644
--- a/sphinx/themes/basic/static/basic.css_t
+++ b/sphinx/themes/basic/static/basic.css_t
@@ -680,7 +680,7 @@ table.highlighttable td {
}
table.highlighttable td.linenos {
- padding: 0 0.5em;
+ padding-right: 0.5em;
}
table.highlighttable td.code {
diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t
index cac283400..21caac0fd 100644
--- a/sphinx/themes/haiku/static/haiku.css_t
+++ b/sphinx/themes/haiku/static/haiku.css_t
@@ -319,7 +319,6 @@ pre {
border-width: thin;
margin: 0 0 12px 0;
padding: 0.8em;
- background-color: #f0f0f0;
}
hr {
diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t
index 13c64467b..34893b86a 100644
--- a/sphinx/themes/nature/static/nature.css_t
+++ b/sphinx/themes/nature/static/nature.css_t
@@ -184,10 +184,6 @@ div.admonition p.admonition-title + p {
display: inline;
}
-div.highlight{
- background-color: white;
-}
-
div.note {
background-color: #eee;
border: 1px solid #ccc;
@@ -217,8 +213,6 @@ p.admonition-title:after {
pre {
padding: 10px;
- background-color: White;
- color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.1em;
diff --git a/sphinx/themes/pyramid/static/pyramid.css_t b/sphinx/themes/pyramid/static/pyramid.css_t
index 48cb2ab6f..dafd898d5 100644
--- a/sphinx/themes/pyramid/static/pyramid.css_t
+++ b/sphinx/themes/pyramid/static/pyramid.css_t
@@ -229,10 +229,6 @@ div.admonition {
padding: 10px 20px 10px 60px;
}
-div.highlight{
- background-color: white;
-}
-
div.note {
border: 2px solid #7a9eec;
border-right-style: none;
@@ -286,8 +282,6 @@ p.admonition-title:after {
pre {
padding: 10px;
- background-color: #fafafa;
- color: #222;
line-height: 1.2em;
border: 2px solid #C6C9CB;
font-size: 1.1em;
diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t
index b01d4ad9f..e484f8c4f 100644
--- a/sphinx/themes/scrolls/static/scrolls.css_t
+++ b/sphinx/themes/scrolls/static/scrolls.css_t
@@ -188,7 +188,7 @@ a:hover {
}
pre {
- background: #ededed url(metal.png);
+ background-image: url(metal.png);
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
padding: 5px;
diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t
index 626d8c3f2..f9961ae36 100644
--- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t
+++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css_t
@@ -247,7 +247,6 @@ pre {
line-height: 120%;
padding: 0.5em;
border: 1px solid #ccc;
- background-color: #f8f8f8;
}
pre a {
diff --git a/sphinx/themes/traditional/static/traditional.css_t b/sphinx/themes/traditional/static/traditional.css_t
index 68719dcd1..0120f83a5 100644
--- a/sphinx/themes/traditional/static/traditional.css_t
+++ b/sphinx/themes/traditional/static/traditional.css_t
@@ -632,7 +632,6 @@ th {
pre {
font-family: monospace;
padding: 5px;
- color: #00008b;
border-left: none;
border-right: none;
}
diff --git a/sphinx/util/console.py b/sphinx/util/console.py
index 98563f58e..d429be602 100644
--- a/sphinx/util/console.py
+++ b/sphinx/util/console.py
@@ -35,8 +35,7 @@ def get_terminal_width() -> int:
import termios
import fcntl
import struct
- call = fcntl.ioctl(0, termios.TIOCGWINSZ, # type: ignore
- struct.pack('hhhh', 0, 0, 0, 0))
+ call = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hhhh', 0, 0, 0, 0))
height, width = struct.unpack('hhhh', call)[:2]
terminal_width = width
except Exception:
diff --git a/sphinx/util/images.py b/sphinx/util/images.py
index 115007d31..0ddf64908 100644
--- a/sphinx/util/images.py
+++ b/sphinx/util/images.py
@@ -54,7 +54,7 @@ def get_image_size(filename: str) -> Optional[Tuple[int, int]]:
def guess_mimetype_for_stream(stream: IO, default: Optional[str] = None) -> Optional[str]:
- imgtype = imghdr.what(stream) # type: ignore
+ imgtype = imghdr.what(stream)
if imgtype:
return 'image/' + imgtype
else:
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 6ba698eed..d4928c847 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -13,6 +13,7 @@ import enum
import inspect
import re
import sys
+import types
import typing
import warnings
from functools import partial, partialmethod
@@ -304,6 +305,18 @@ def isproperty(obj: Any) -> bool:
return isinstance(obj, property)
+def isgenericalias(obj: Any) -> bool:
+ """Check if the object is GenericAlias."""
+ if (hasattr(typing, '_GenericAlias') and # only for py37+
+ isinstance(obj, typing._GenericAlias)): # type: ignore
+ return True
+ elif (hasattr(types, 'GenericAlias') and # only for py39+
+ isinstance(obj, types.GenericAlias)): # type: ignore
+ return True
+ else:
+ return False
+
+
def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
"""A getattr() that turns all exceptions into AttributeErrors."""
try:
@@ -514,23 +527,42 @@ def stringify_signature(sig: inspect.Signature, show_annotation: bool = True,
def signature_from_str(signature: str) -> inspect.Signature:
"""Create a Signature object from string."""
module = ast.parse('def func' + signature + ': pass')
- definition = cast(ast.FunctionDef, module.body[0]) # type: ignore
+ function = cast(ast.FunctionDef, module.body[0]) # type: ignore
+
+ return signature_from_ast(function)
+
- # parameters
- args = definition.args
+def signature_from_ast(node: ast.FunctionDef) -> inspect.Signature:
+ """Create a Signature object from AST *node*."""
+ args = node.args
+ defaults = list(args.defaults)
params = []
+ if hasattr(args, "posonlyargs"):
+ posonlyargs = len(args.posonlyargs) # type: ignore
+ positionals = posonlyargs + len(args.args)
+ else:
+ posonlyargs = 0
+ positionals = len(args.args)
+
+ for _ in range(len(defaults), positionals):
+ defaults.insert(0, Parameter.empty)
if hasattr(args, "posonlyargs"):
- for arg in args.posonlyargs: # type: ignore
+ for i, arg in enumerate(args.posonlyargs): # type: ignore
+ if defaults[i] is Parameter.empty:
+ default = Parameter.empty
+ else:
+ default = ast_unparse(defaults[i])
+
annotation = ast_unparse(arg.annotation) or Parameter.empty
params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
- annotation=annotation))
+ default=default, annotation=annotation))
for i, arg in enumerate(args.args):
- if len(args.args) - i <= len(args.defaults):
- default = ast_unparse(args.defaults[-len(args.args) + i])
- else:
+ if defaults[i + posonlyargs] is Parameter.empty:
default = Parameter.empty
+ else:
+ default = ast_unparse(defaults[i + posonlyargs])
annotation = ast_unparse(arg.annotation) or Parameter.empty
params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
@@ -552,7 +584,7 @@ def signature_from_str(signature: str) -> inspect.Signature:
params.append(Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD,
annotation=annotation))
- return_annotation = ast_unparse(definition.returns) or Parameter.empty
+ return_annotation = ast_unparse(node.returns) or Parameter.empty
return inspect.Signature(params, return_annotation=return_annotation)
@@ -726,7 +758,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
# This tries to obtain the docstring from super classes.
for basecls in getattr(cls, '__mro__', []):
meth = safe_getattr(basecls, name, None)
- if meth:
+ if meth is not None:
doc = inspect.getdoc(meth)
if doc:
break
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index a18eac580..1ec3a19d0 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -29,6 +29,7 @@ if False:
# For type annotation
from typing import Type # for python3.5.1
from sphinx.builders import Builder
+ from sphinx.domain import IndexEntry
from sphinx.environment import BuildEnvironment
from sphinx.utils.tags import Tags
@@ -313,7 +314,7 @@ def get_prev_node(node: Node) -> Node:
return None
-def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List[str]]]:
+def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List["IndexEntry"]]]: # NOQA
"""Traverse translatable index node from a document tree."""
for node in doctree.traverse(NodeMatcher(addnodes.index, inline=False)): # type: addnodes.index # NOQA
if 'raw_entries' in node:
diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py
index 6decc1cef..664387cac 100644
--- a/sphinx/util/pycompat.py
+++ b/sphinx/util/pycompat.py
@@ -90,7 +90,7 @@ def execfile_(filepath: str, _globals: Any, open: Callable = open) -> None:
deprecated_alias('sphinx.util.pycompat',
{
- 'NoneType': NoneType, # type: ignore
+ 'NoneType': NoneType,
'TextIOWrapper': io.TextIOWrapper,
'htmlescape': html.escape,
'indent': textwrap.indent,
diff --git a/sphinx/util/template.py b/sphinx/util/template.py
index 2449a60a1..18047d687 100644
--- a/sphinx/util/template.py
+++ b/sphinx/util/template.py
@@ -28,7 +28,7 @@ class BaseRenderer:
def __init__(self, loader: BaseLoader = None) -> None:
self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n'])
self.env.filters['repr'] = repr
- self.env.install_gettext_translations(get_translator()) # type: ignore
+ self.env.install_gettext_translations(get_translator())
def render(self, template_name: str, context: Dict) -> str:
return self.env.get_template(template_name).render(context)
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index de92e7593..18b363eca 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -53,7 +53,7 @@ def stringify(annotation: Any) -> str:
return annotation.__name__
elif not annotation:
return repr(annotation)
- elif annotation is NoneType: # type: ignore
+ elif annotation is NoneType:
return 'None'
elif (getattr(annotation, '__module__', None) == 'builtins' and
hasattr(annotation, '__qualname__')):
@@ -91,7 +91,7 @@ def _stringify_py37(annotation: Any) -> str:
if getattr(annotation, '__args__', None):
if qualname == 'Union':
- if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: # type: ignore # NOQA
+ if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
return 'Optional[Union[%s]]' % args
@@ -165,7 +165,7 @@ def _stringify_py36(annotation: Any) -> str:
hasattr(annotation, '__union_params__')): # for Python 3.5
params = annotation.__union_params__
if params is not None:
- if len(params) == 2 and params[1] is NoneType: # type: ignore
+ if len(params) == 2 and params[1] is NoneType:
return 'Optional[%s]' % stringify(params[0])
else:
param_str = ', '.join(stringify(p) for p in params)
@@ -174,7 +174,7 @@ def _stringify_py36(annotation: Any) -> str:
annotation.__origin__ is typing.Union): # for Python 3.5.2+
params = annotation.__args__
if params is not None:
- if len(params) > 1 and params[-1] is NoneType: # type: ignore
+ if len(params) > 1 and params[-1] is NoneType:
if len(params) > 2:
param_str = ", ".join(stringify(p) for p in params[:-1])
return 'Optional[Union[%s]]' % param_str
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index e3ddedccf..6e7f5021b 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -1431,6 +1431,8 @@ class LaTeXTranslator(SphinxTranslator):
if 'refuri' in node:
return
+ if 'anonymous' in node:
+ return
if node.get('refid'):
prev_node = get_prev_node(node)
if isinstance(prev_node, nodes.reference) and node['refid'] == prev_node['refid']: