Question: <ins>"""    sphinx.ext.autodoc.preserve_defaults    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    Preserve the default argument values of function signatures in source code    and keep them not evaluated for readability.    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.    :license: BSD, see LICENSE for details."""import astimport inspectfrom typing import Any, Dictfrom sphinx.application import Sphinxfrom sphinx.locale import __from sphinx.pycode.ast import parse as ast_parsefrom sphinx.pycode.ast import unparse as ast_unparsefrom sphinx.util import logginglogger = logging.getLogger(__name__)class DefaultValue:    def __init__(self, name: str) -> None:        self.name = name    def __repr__(self) -> str:        return self.namedef get_function_def(obj: Any) -> ast.FunctionDef:    """Get FunctionDef object from living object.    This tries to parse original code for living object and returns    AST node for given *obj*.    """    try:        source = inspect.getsource(obj)        if source.startswith((' ', r'\t')):            # subject is placed inside class or block.  To read its docstring,            # this adds if-block before the declaration.            module = ast_parse('if True:\n' + source)            return module.body[0].body[0]  # type: ignore        else:            module = ast_parse(source)            return module.body[0]  # type: ignore    except (OSError, TypeError):  # failed to load source code        return Nonedef update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:    """Update defvalue info of *obj* using type_comments."""    if not app.config.autodoc_preserve_defaults:        return    try:        function = get_function_def(obj)        if function.args.defaults or function.args.kw_defaults:            sig = inspect.signature(obj)            defaults = list(function.args.defaults)            kw_defaults = list(function.args.kw_defaults)            parameters = list(sig.parameters.values())            for i, param in enumerate(parameters):                if param.default is not param.empty:                    if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):                        value = DefaultValue(ast_unparse(defaults.pop(0)))  # type: ignore                        parameters[i] = param.replace(default=value)                    else:                        value = DefaultValue(ast_unparse(kw_defaults.pop(0)))  # type: ignore                        parameters[i] = param.replace(default=value)            sig = sig.replace(parameters=parameters)            obj.__signature__ = sig    except (AttributeError, TypeError):        # failed to update signature (ex. built-in or extension types)        pass    except NotImplementedError as exc:  # failed to ast.unparse()        logger.warning(__("Failed to parse a default argument value for %r: %s"), obj, exc)def setup(app: Sphinx) -> Dict[str, Any]:    app.add_config_value('autodoc_preserve_defaults', False, True)    app.connect('autodoc-before-process-signature', update_defvalue)    return {        'version': '1.0',        'parallel_read_safe': True    }</ins>


Question: try:         for cls in getmro(subject):            try:                modname = safe_getattr(cls, '__module__')                qualname = safe_getattr(cls, '__qualname__')                analyzer = ModuleAnalyzer.for_module(modname)                analyzer.analyze()            except AttributeError:                qualname = None                analyzer = None            except PycodeError:                analyzer = None             # annotation only member (ex. attr: int)             for name in getannotations(cls):                 name = unmangle(cls, name)                 if name and name not in members:                    members[name] = ObjectMember(name, INSTANCEATTR, class_=cls)                    if analyzer and (qualname, name) in analyzer.attr_docs:                        docstring = '\n'.join(analyzer.attr_docs[qualname, name])                    else:                        docstring = None                    members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,                                                 docstring=docstring)              # append instance attributes (cf. self.attr1) if analyzer knows            try:                modname = safe_getattr(cls, '__module__')                qualname = safe_getattr(cls, '__qualname__')                analyzer = ModuleAnalyzer.for_module(modname)                analyzer.analyze()            if analyzer:                 for (ns, name), docstring in analyzer.attr_docs.items():                     if ns == qualname and name not in members:                         members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,                                                      docstring='\n'.join(docstring))            except (AttributeError, PycodeError):                pass     except AttributeError:         pass

autodoc: The annotation only member in superclass is treated as "undocumented" Describe the bug autodoc: The annotation only member in superclass is treated as "undocumented". To Reproduce is not documented. It will be shown if I give option to the autoclass directive call. It seems the attribute is treated as undocumented. Expected behavior It should be shown. Your project No Screenshots No Environment info OS: Mac Python version: 3.9.1 Sphinx version: HEAD of 3.x Sphinx extensions: Extra tools: No Additional context No


Question: def visit_footnote(self, node: Element) -> None:         self.in_footnote += 1         label = cast(nodes.label, node[0])        if 'auto' not in node:            self.body.append('\\sphinxstepexplicit ')         if self.in_parsed_literal:             self.body.append('\\begin{footnote}[%s]' % label.astext())         else:             self.body.append('%\n')             self.body.append('\\begin{footnote}[%s]' % label.astext())        if 'auto' not in node:            self.body.append('\\phantomsection'                             '\\label{\\thesphinxscope.%s}%%\n' % label.astext())         self.body.append('\\sphinxAtStartFootnote\n')      def depart_footnote(self, node: Element) -> None:

[LaTex] code-block printed out of margin this is not handled correctly currently: long hex strings ! code: ` link to rst file: Originally posted by in


Question: label = cast(nodes.label, node[0])         self.body.append('%\n')         self.body.append('\\begin{footnotetext}[%s]'                         '\\sphinxAtStartFootnote\n' % label.astext())                         '\\phantomsection\\label{\\thesphinxscope.%s}%%\n'                         % (label.astext(), label.astext()))        self.body.append('\\sphinxAtStartFootnote\n')      def depart_footnotetext(self, node: Element) -> None:         # the \ignorespaces in particular for after table header use


Question: |   \+\+ | --     |   ->\*? | \,     |   (<<|>>)=? | && | \|\|<ins>    |   <=></ins>     |   [!<>=/*%+|&^~-]=?     |   (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b) ''')

Support C++20's spaceship operator C++20 has the new spaceship operator for three way comparisons which can also be (not sure if the latter matters to Sphinx). I hope this is a problem with Sphinx at all and not with the Breathe extension I'm also using. For example, this reST code in Sphinx currently leads to the following warning/error


Question: '>': 'gt',     '<=': 'le',     '>=': 'ge',<ins>    '<=>': 'ss',</ins>     '!': 'nt', 'not': 'nt',     '&&': 'aa', 'and': 'aa',     '||': 'oo', 'or': 'oo',


Question: ['^', 'xor'],     ['&', 'bitand'],     ['==', '!=', 'not_eq'],    ['<=', '>=', '<', '>'],    ['<=>', '<=', '>=', '<', '>'],     ['<<', '>>'],     ['+', '-'],     ['*', '/', '%'],


Question: # exclusive-or   = and              ^         # and            = equality         &         # equality       = relational       ==, !=        # relational     = shift            <, >, <=, >=        # relational     = shift            <, >, <=, >=, <=>         # shift          = additive         <<, >>         # additive       = multiplicative   +, -         # multiplicative = pm               *, /, %


Question: :license: BSD, see LICENSE for details. """ import os import warningsfrom os import path from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast  from docutils import nodes

Absolute/"source-relative" imports for csv-table :file: Describe the bug To be honest, I am not sure if this can be considered a bug, or if it is rather a feature request. Sorry about that. When using the directive, the use of with absolute paths are really absolute, unlike with (eg) the directive, where absolute paths are treated relative to the source directory (herein called "source-relative"). I do understand that there is a difference in the 2 cases, because with the path is not specified in . Yet, I do not see a possibility to mimic this behavior in the directive. To Reproduce A file in : Relative imports: are treated, as expected, relative to the file: Absolute imports: are treated, opposed to my expectations, like real absolute paths: and not like relative-to-source paths. Expected behavior I would expect this to work like absolute paths in the (eg) directive. But as stated in the beginning, probably I am wrong with my expectation, and this should be a feature request to add an option to use "source-relative" paths with the directive. Environment info OS: Win Python version: 3.8.5 Sphinx version: 3.2.1


Question: from sphinx.deprecation import RemovedInSphinx60Warning from sphinx.directives import optional_int from sphinx.domains.math import MathDomainfrom sphinx.locale import __from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_infofrom sphinx.util.osutil import SEP, os_path, relpath  if TYPE_CHECKING:     from sphinx.application import Sphinx  logger = logging.getLogger(__name__) class Figure(images.Figure):     """The figure directive which applies `:name:` option to the figure node     instead of the image node.


Question: class CSVTable(tables.CSVTable):    """The csv-table directive which sets source and line information to its caption.    Only for docutils-0.13 or older version."""    """The csv-table directive which searches a CSV file from Sphinx project's source    directory when an absolute path is given via :file: option.    """      def run(self) -> List[Node]:        warnings.warn('CSVTable is deprecated.',                      RemovedInSphinx60Warning)        return super().run()        if 'file' in self.options and self.options['file'].startswith((SEP, os.sep)):            env = self.state.document.settings.env            filename = self.options['file']            if path.exists(filename):                logger.warning(__('":file:" option for csv-table directive now recognizes '                                  'an absolute path as a relative path from source directory. '                                  'Please update your document.'),                               location=(env.docname, self.lineno))            else:                abspath = path.join(env.srcdir, os_path(self.options['file'][1:]))                docdir = path.dirname(env.doc2path(env.docname))                self.options['file'] = relpath(abspath, docdir)     def make_title(self) -> Tuple[nodes.title, List[system_message]]:        title, message = super().make_title()        if title:            set_source_info(self, title)        return title, message        return super().run()   class ListTable(tables.ListTable):


Question: def setup(app: "Sphinx") -> Dict[str, Any]:     directives.register_directive('figure', Figure)     directives.register_directive('meta', Meta)<ins>    directives.register_directive('csv-table', CSVTable)</ins>     directives.register_directive('code', Code)     directives.register_directive('math', MathDirective)


Question: import sys import typing from struct import Struct<ins>from types import TracebackType</ins> from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TypeVar, Union  from docutils import nodes

Property gets linked instead of external type in class with inline type annotations Describe the bug The project is using autodoc with inline Python type annotations. Given the following class (snipped to the relevant parts): The class and method get rendered : ! In the class, the gets rendered as and is not linked. In the method, the gets rendered as and links to the property. Expected behavior I expect it to show as . A is also OK I guess (if it's meant to refer to a "traceback object"), but in that case it should link to or instead of the local property. To Reproduce I can try to create a minimal reproduction if the above is not sufficient or gets outdated. Your project Environment info OS: Linux Python version: 3.9.2 Sphinx version: 3.5.2 Sphinx extensions:


Question: from typing import Type  # NOQA # for python3.5.1  <ins># builtin classes that have incorrect __module__INVALID_BUILTIN_CLASSES = {    Struct: 'struct.Struct',  # Before Python 3.9    TracebackType: 'types.TracebackType',}</ins> # Text like nodes which are initialized with text and rawsource TextlikeNode = Union[nodes.Text, nodes.TextElement]


Question: return ':obj:`None`'     elif cls is Ellipsis:         return '...'    elif cls is Struct:        # Before Python 3.9, struct.Struct class has incorrect __module__.        return ':class:`struct.Struct`'    elif cls in INVALID_BUILTIN_CLASSES:        return ':class:`%s`' % INVALID_BUILTIN_CLASSES[cls]     elif inspect.isNewType(cls):         return ':class:`%s`' % cls.__name__     elif types_Union and isinstance(cls, types_Union):


Question: return repr(annotation)     elif annotation is NoneType:         return 'None'    elif annotation in INVALID_BUILTIN_CLASSES:        return INVALID_BUILTIN_CLASSES[annotation]     elif (getattr(annotation, '__module__', None) == 'builtins' and           hasattr(annotation, '__qualname__')):         return annotation.__qualname__     elif annotation is Ellipsis:         return '...'    elif annotation is Struct:        # Before Python 3.9, struct.Struct class has incorrect __module__.        return 'struct.Struct'      if sys.version_info >= (3, 7):  # py37+         return _stringify_py37(annotation)


Question: def make_xrefs(self, rolename: str, domain: str, target: str,                    innernode: Type[TextlikeNode] = nodes.emphasis,                    contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:        delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+)'        delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\.\.\.)'         delims_re = re.compile(delims)         sub_targets = re.split(delims, target)

WARNING: py:class reference target not found: .. Describe the bug I think this might be referring to the ellipsis but I'm not sure at present. Note that I've already tracked the other warnings (outcomes and event type) to other issues. This time I did remember to try latest 3.x (). It showed the same issue. To Reproduce Steps to reproduce the behavior: <details<summarySteps with output including complete traceback</summary</detailsExpected behavior I'm not completely sure but at least a reference to a specific thing in my code. If it is referring to the ellipsis then perhaps it just shouldn't have an issue at all. I'm guessing it should just be a plain in the output. It could link to but that doesn't really describe it's meaning in typing and the generic type for which it is a parameter would already be linked. Your project Environment info OS: Ubuntu Linux 20.04 Python version: 3.8.3 Sphinx version: 3.2.0 Sphinx extensions: sphinxqtdocumentation sphinxcontribtrio


Question: docname: str     node_id: str     objtype: str    canonical: bool    aliased: bool   class ModuleEntry(NamedTuple):

autodoc: duplication warning on documenting aliased object Describe the bug autodoc: duplication warning on documenting aliased object To Reproduce Expected behavior No warning Your project N/A Screenshots N/A Environment info OS: Mac Python version: 3.9.4 Sphinx version: HEAD of 4.0.x Sphinx extensions: Extra tools: No Additional context No


Question: canonical_name = self.options.get('canonical')         if canonical_name:            domain.note_object(canonical_name, self.objtype, node_id, canonical=True,            domain.note_object(canonical_name, self.objtype, node_id, aliased=True,                                location=signode)          if 'noindexentry' not in self.options:


Question: return self.data.setdefault('objects', {})  # fullname -> ObjectEntry      def note_object(self, name: str, objtype: str, node_id: str,                    canonical: bool = False, location: Any = None) -> None:                    aliased: bool = False, location: Any = None) -> None:         """Note a python object for cross reference.          .. versionadded:: 2.1         """         if name in self.objects:             other = self.objects[name]            logger.warning(__('duplicate object description of %s, '                              'other instance in %s, use :noindex: for one of them'),                           name, other.docname, location=location)        self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, canonical)            if other.aliased and aliased is False:                # The original definition found. Override it!                pass            elif other.aliased is False and aliased:                # The original definition is already registered.                return            else:                # duplicated                logger.warning(__('duplicate object description of %s, '                                  'other instance in %s, use :noindex: for one of them'),                               name, other.docname, location=location)        self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased)      @property     def modules(self) -> Dict[str, ModuleEntry]:


Question: yield (modname, modname, 'module', mod.docname, mod.node_id, 0)         for refname, obj in self.objects.items():             if obj.objtype != 'module':  # modules are already handled                if obj.canonical:                    # canonical names are not full-text searchable.                if obj.aliased:                    # aliased names are not full-text searchable.                     yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1)                 else:                     yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)


Question: def __contains__(self, item: Any) -> bool:         return True <ins>    def append(self, item: Any) -> None:        pass  # nothing</ins>  class _Empty:     """A special value for :exclude-members: that never matches to any member."""

Omitting constructor signature from class header using I'm a bit surprised to see that (in the Python domain) will create a heading like which seems quite unintuitive to me, as it mixes two concepts: the declaration of "class Foo", with a constructor call "Foo(args)". How can I suppress the (automatic) addition of the constructor signature to the generated heading ? I actually want to document the constructor with an additional nested directive, and I also have an overloaded method, making the above all the more confusing. Am I missing or misunderstanding something ?


Question: def __init__(self, *args: Any) -> None:         super().__init__(*args)<ins>        if self.config.autodoc_class_signature == 'separated':            # show __init__() method            if self.options.special_members is None:                self.options['special-members'] = {'__new__', '__init__'}            else:                self.options.special_members.append('__new__')                self.options.special_members.append('__init__')</ins>         merge_members_option(self.options)      @classmethod


Question: def format_signature(self, **kwargs: Any) -> str:         if self.doc_as_attr:             return ''<ins>        if self.config.autodoc_class_signature == 'separated':            # do not show signatures            return ''</ins>          sig = super().format_signature()         sigs = []


Question: else:             return None <ins>    def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:        if self.objpath[-1] == '__init__':            docstring = getdoc(self.object, self.get_attr,                               self.config.autodoc_inherit_docstrings,                               self.parent, self.object_name)            if (docstring is not None and                (docstring == object.__init__.__doc__ or  # for pypy                 docstring.strip() == object.__init__.__doc__)):  # for !pypy                docstring = None            if docstring:                tab_width = self.directive.state.document.settings.tab_width                return [prepare_docstring(docstring, tabsize=tab_width)]            else:                return []        elif self.objpath[-1] == '__new__':            __new__ = self.get_attr(self.object, '__new__', None)            if __new__:                docstring = getdoc(__new__, self.get_attr,                                   self.config.autodoc_inherit_docstrings,                                   self.parent, self.object_name)                if (docstring is not None and                    (docstring == object.__new__.__doc__ or  # for pypy                     docstring.strip() == object.__new__.__doc__)):  # for !pypy                    docstring = None            if docstring:                tab_width = self.directive.state.document.settings.tab_width                return [prepare_docstring(docstring, tabsize=tab_width)]            else:                return []        else:            return super().get_doc()</ins>  class NonDataDescriptorMixin(DataDocumenterMixinBase):     """


Question: app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))     app.add_config_value('autodoc_member_order', 'alphabetical', True,                          ENUM('alphabetic', 'alphabetical', 'bysource', 'groupwise'))    app.add_config_value('autodoc_class_signature', 'mixed', True, ENUM('mixed', 'separated'))     app.add_config_value('autodoc_default_options', {}, True)     app.add_config_value('autodoc_docstring_signature', True, True)     app.add_config_value('autodoc_mock_imports', [], True)diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py-- a/sphinx/ext/autosummary/__init__.py++ b/sphinx/ext/autosummary/__init__.py


Question: document = Struct(settings=settings)         env = BuildEnvironment()         env.config = Config()<ins>        env.config.add('autodoc_class_signature', 'mixed', True, None)</ins>         state = Struct(document=document)         super().__init__(env, None, Options(), 0, state)


Question: # if isattr is True, the member is documented as an attribute             if member is INSTANCEATTR:                 isattr = True<ins>            elif (namespace, membername) in attr_docs:                isattr = True</ins>             else:                 isattr = False

Special member is not documented with automodule Describe the bug If I have a module which has a special attribute (starting with a double underscore) it will not be documented by automodule even if it's specified in :special-members:. According to my research it's because it's not identified as an attribute. If a non-special attribute is used, everything works fine. To Reproduce To execute the build I use Expected behavior The attribute should be documented. Environment info OS: Linux Python version: 3.7.1 Sphinx version: 3.5.4


Question: else:                     # keep documented attributes                     keep = True<del>                isattr = True</del>             elif want_all and isprivate:                 if has_doc or self.options.undoc_members:                     if self.options.private_members is None:


Question: if not matches:             return None         elif len(matches) > 1:            logger.warning(__('more than one target found for cross-reference %r: %s'),                           target, ', '.join(match[0] for match in matches),                           type='ref', subtype='python', location=node)            canonicals = [m for m in matches if not m[1].aliased]            if len(canonicals) == 1:                matches = canonicals            else:                logger.warning(__('more than one target found for cross-reference %r: %s'),                               target, ', '.join(match[0] for match in matches),                               type='ref', subtype='python', location=node)         name, obj = matches[0]          if obj[2] == 'module':

Sphinx 4.0 finds more than one target for cross-reference Hello everyone, Describe the bug After upgrading to 4.0.0 we have discovered that existing code is raising a warning with . In 3.5.4 we did not see this warning To Reproduce I have set up a minimal reproduction repository here: Steps to reproduce the behavior: Output of the sphinx-build command: Expected behavior no warning appears Your project Environment info OS: Linux Python version: 3.9.4 Sphinx version: 4.0.0 Sphinx extensions: Additional context I have bisected the issue to be introduced by Thanks verry much


Question: def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:         if self.doc_as_attr:             # Don't show the docstring of the class when it is an alias.            return None            comment = self.get_variable_comment()            if comment:                return []            else:                return None          lines = getattr(self, '_new_docstrings', None)         if lines is not None:

Inconsistent behaviour with type alias documentation (not overwriting all the default messages, just some) Describe the bug Hello, I have 3 muiltiline docstrings for type aliases (using the next-line documentation syntax). For 1 one them the docstring is correctly shown in the rendered HTML, but for 2 of them, the docstrings are ignored and the only thing shown is the text. I suppose this is related to , but I might be doing something wrong here (so if you could point me out in the correct direction that would be very good). To Reproduce The following is a reduced example of something happening in : a directory with : I run: the path adjustment in to the generated , and to the toctree in . opening in the browser should show the reported inconsistency. Expected behavior The docs should show the contents in the docstrings for all the type aliases instead of the the default text. Your project Screenshots ! Environment info OS: Win10 WSL: Python version: 3.6.9 Sphinx version: 3.1.2 Sphinx extensions: Additional context Possibly related to


Question: tab_width = self.directive.state.document.settings.tab_width         return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings]     def get_variable_comment(self) -> Optional[List[str]]:        try:            key = ('', '.'.join(self.objpath))            analyzer = ModuleAnalyzer.for_module(self.get_real_modname())            analyzer.analyze()            return list(self.analyzer.attr_docs.get(key, []))        except PycodeError:            return None     def add_content(self, more_content: Optional[StringList], no_docstring: bool = False                     ) -> None:        if self.doc_as_attr:        if self.doc_as_attr and not self.get_variable_comment():             try:                 more_content = StringList([_('alias of %s') % restify(self.object)], source='')             except AttributeError:


Question: # also support syntax like ``:param type name:``             if typedesc.is_typed:                 try:                    argtype, argname = fieldarg.split(None, 1)                    argtype, argname = fieldarg.rsplit(None, 1)                 except ValueError:                     pass                 else:

Doc rendering is incorrect when :param has datatype dict(str,str) Describe the bug I have a parameter defined under docstring of a method as:- :param dict(str, str) opcmeta: (optional) Which is being incorrectly rendered in the generated docs as:- str) opcmeta (dict(str,) –(optional) To Reproduce Create any method with the docstring containg the above param Expected behavior The param should be rendered in the generated docs as:- opc_meta (dict(str,str)) – (optional) Your project Screenshots <img width="612" alt="Screen Shot 2021-05-12 at 12 30 50 PM" src="https://user-"<img width="681" alt="Screen Shot 2021-05-12 at 12 32 25 PM" src="https://user-"Environment info OS: Mac Python version: 3.9.0 Sphinx version: 4.0.1 Sphinx extensions: ["", "", "", "autodocsumm"] Extra tools: Browser Firefox. Additional context N/A


Question: docsettings.section = section              if self.config.man_make_section_directory:                ensuredir(path.join(self.outdir, str(section)))                targetname = '%s/%s.%s' % (section, name, section)                dirname = 'man%s' % section                ensuredir(path.join(self.outdir, dirname))                targetname = '%s/%s.%s' % (dirname, name, section)             else:                 targetname = '%s.%s' % (name, section)

manmakesectiondirectory should not be enabled by default Enabling by default in breaks projects relying on the previous behavior. This is a serious problem for Linux distributions that will end up with misplaced and unusable man pages. Please consider keeping it disabled by default; the benefit of being able to use MANPATH in the output directory does not justify this kind of breakage. I also noticed that the current implementation generates paths like instead of . Only the latter can be used with MANPATH which appears to be the main motivation behind . Examples of breakage I've seen so far (and we've only had sphinx 4.0.x in Arch Linux for three days): does not expect the section subdirectory and results in man pages for built-in shell commands being installed to instead of and also fails to filter out , and which are meant to be installed to . copies the output directory to resulting in paths like (note the additional directory).


Question: # add inheritance info, if wanted         if not self.doc_as_attr and self.options.show_inheritance:            sourcename = self.get_sourcename()            self.add_line('', sourcename)             if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__):                 # A subclass of generic types                 # refs: PEP-560 <https://www.python.org/dev/peps/pep-0560/>                bases = [restify(cls) for cls in self.object.__orig_bases__]                self.add_line('   ' + _('Bases: %s') % ', '.join(bases), sourcename)                bases = list(self.object.__orig_bases__)             elif hasattr(self.object, '__bases__') and len(self.object.__bases__):                 # A normal class                bases = [restify(cls) for cls in self.object.__bases__]                self.add_line('   ' + _('Bases: %s') % ', '.join(bases), sourcename)                bases = list(self.object.__bases__)            else:                bases = []            self.env.events.emit('autodoc-process-bases',                                 self.fullname, self.object, self.options, bases)            base_classes = [restify(cls) for cls in bases]            sourcename = self.get_sourcename()            self.add_line('', sourcename)            self.add_line('   ' + _('Bases: %s') % ', '.join(base_classes), sourcename)      def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:         members = get_class_members(self.object, self.objpath, self.get_attr)

New hook to customize base list I would like to change the formatting of the base list for classes. Specifially I would like to provide information about parameterized types (e.g. ]). See agronholm/sphinx-autodoc-typehints for how I want to use it. For that I need a new hook/event similar to the existing . I propose the signature . The first five arguments are exactly the same as for the existing events. The last one is the list of formatted strings generated in for the bases. It can be modified to change what is output. Alternatively, to provide even more freedom, the hook can return a string. This string is then inserted instead of the "Bases: ..." line. I can provide an implementation in a pull request, if you like the idea.


Question: app.add_event('autodoc-process-docstring')     app.add_event('autodoc-process-signature')     app.add_event('autodoc-skip-member')<ins>    app.add_event('autodoc-process-bases')</ins>      app.connect('config-inited', migrate_autodoc_member_order, priority=800)


Question: except KeyError:                 text, color = ('with unknown code', purple)             linkstat['text'] = text            logger.info(color('redirect  ') + result.uri +                        color(' - ' + text + ' to ' + result.message))            if self.config.linkcheck_allowed_redirects:                logger.warning('redirect  ' + result.uri + ' - ' + text + ' to ' +                               result.message, location=(filename, result.lineno))            else:                logger.info(color('redirect  ') + result.uri +                            color(' - ' + text + ' to ' + result.message))             self.write_entry('redirected ' + text, result.docname, filename,                              result.lineno, result.uri + ' to ' + result.message)         else:

Link checker should be able to prohibit unknown redirects Is your feature request related to a problem? Please describe. A lot of links become stale or move. Good websites will provide redirects to the correct new location or return an HTTP error code. Bad websites will redirect to an unrelated page or the root of the website. Preventing all redirects does not allow links to URLs like which redirects to It needs to be possible to allow these redirects but disallow others. Describe the solution you'd like It should be possible to prohibit unknown redirects by listing all of the allowed redirects as pairs of URLs. Describe alternatives you've considered Post-process by removing filenames and line numbers then sorting it and comparing it with known good output. Additional context A link to (which used to work) now redirects to Linkcheck allows this but the original link is not valid and needs to be updated to the article's new URL of Linkcheck should be able to report an error for this redirect.


Question: new_url = response.url                 if anchor:                     new_url += '#' + anchor                # history contains any redirects, get last                if response.history:                if allowed_redirect(req_url, new_url):                    return 'working', '', 0                elif response.history:                    # history contains any redirects, get last                     code = response.history[-1].status_code                     return 'redirected', new_url, code                 else:                     return 'redirected', new_url, 0         def allowed_redirect(url: str, new_url: str) -> bool:            for from_url, to_url in self.config.linkcheck_allowed_redirects.items():                if from_url.match(url) and to_url.match(new_url):                    return True            return False         def check(docname: str) -> Tuple[str, str, int]:             # check for various conditions without bothering the network             if len(uri) == 0 or uri.startswith(('#', 'mailto:', 'tel:')):


Question: return None  def compile_linkcheck_allowed_redirects(app: Sphinx, config: Config) -> None:    """Compile patterns in linkcheck_allowed_redirects to the regexp objects."""    for url, pattern in list(app.config.linkcheck_allowed_redirects.items()):        try:            app.config.linkcheck_allowed_redirects[re.compile(url)] = re.compile(pattern)        except re.error as exc:            logger.warning(__('Failed to compile regex in linkcheck_allowed_redirects: %r %s'),                           exc.pattern, exc.msg)        finally:            # Remove the original regexp-string            app.config.linkcheck_allowed_redirects.pop(url) def setup(app: Sphinx) -> Dict[str, Any]:     app.add_builder(CheckExternalLinksBuilder)     app.add_post_transform(HyperlinkCollector)      app.add_config_value('linkcheck_ignore', [], None)    app.add_config_value('linkcheck_allowed_redirects', {}, None)     app.add_config_value('linkcheck_auth', [], None)     app.add_config_value('linkcheck_request_headers', {}, None)     app.add_config_value('linkcheck_retries', 1, None)


Question: app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None)      app.add_event('linkcheck-process-uri')<ins>    app.connect('config-inited', compile_linkcheck_allowed_redirects, priority=800)</ins>     app.connect('linkcheck-process-uri', rewrite_github_anchor)      return {


Question: :license: BSD, see LICENSE for details. """ from typing import Any, Dict, List, Optional, Tuple, Type, castfrom typing import Any, Dict, List, Optional, Sequence, Tuple, Type, cast  from docutils import nodesfrom docutils.nodes import Elementfrom docutils.nodes import Element, Node  from sphinx import addnodes from sphinx.addnodes import pending_xref

Unknown node: : : : Run: OS: Linux Python version: 3.9.4 Sphinx version: 4.0.1 Sphinx extensions: , , This is similar to the issue that was fixed in . I think, that I've tracked the issue to the plugin. This plugin connects itself to events like this however, their implementation doesn't have the snippet, which eventually leads to the above error. I could submit this as a bug report to the author of , but I wanted to make sure, that this behaviour is intended. It seems to me like a potential antipattern. For example, the above snippet (with minor variations) is already duplicated internally in - - And any plugin that connects to the event must also now add this snippet (which is complicated by the fact that doesn't seem to be available in ). Do you think that maybe the value should be resolved when the event is created in ?


Question: logger = logging.getLogger(__name__) <del>if False:    # For type annotation    from docutils.nodes import Node</del>  class SphinxPostTransform(SphinxTransform):     """A base class of post-transforms.


Question: def run(self, **kwargs: Any) -> None:         for node in self.document.traverse(addnodes.pending_xref):            contnode = cast(nodes.TextElement, node[0].deepcopy())            content = self.find_pending_xref_condition(node, ("resolved", "*"))            if content:                contnode = cast(Element, content[0].deepcopy())            else:                contnode = cast(Element, node[0].deepcopy())             newnode = None              typ = node['reftype']


Question: else:                 newnodes = [contnode]                 if newnode is None and isinstance(node[0], addnodes.pending_xref_condition):                    matched = find_pending_xref_condition(node, "*")                    matched = self.find_pending_xref_condition(node, ("*",))                     if matched:                        newnodes = matched.children                        newnodes = matched                     else:                         logger.warning(__('Could not determine the fallback text for the '                                           'cross-reference. Might be a bug.'), location=node)


Question: msg = __('%r reference target not found: %s') % (typ, target)         logger.warning(msg, location=node, type='ref', subtype=typ) <ins>    def find_pending_xref_condition(self, node: pending_xref, conditions: Sequence[str]                                    ) -> Optional[List[Node]]:        for condition in conditions:            matched = find_pending_xref_condition(node, condition)            if matched:                return matched.children        else:            return None</ins>  class OnlyNodeTransform(SphinxPostTransform):     default_priority = 50


Question: def make_xrefs(self, rolename: str, domain: str, target: str,                    innernode: Type[TextlikeNode] = nodes.emphasis,                    contnode: Node = None, env: BuildEnvironment = None) -> List[Node]:        delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\.\.\.)'        delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\s*\|\s*|\.\.\.)'         delims_re = re.compile(delims)         sub_targets = re.split(delims, target)

[RFE] Support union types specification using | (vertical bar/pipe) Please add a support for specifying multiple types acceptable for a parameter/attribute/variable. Use case: Imagine that there is a function that accepts both and . The docstring would look like: Such a syntax is already supported by e.g. .


Question: from threading import Thread from typing import (Any, Dict, Generator, List, NamedTuple, Optional, Pattern, Set, Tuple,                     Union, cast)from urllib.parse import unquote, urlparsefrom urllib.parse import unquote, urlparse, urlunparse  from docutils import nodes from docutils.nodes import Element

Linkchecker croaks on specific anchors of GitHub-rendered reStructuredText documents Dear Sphinx developers, first things first: Thanks a stack for your paramount work on Sphinx. You already saved many souls of people writing technical documentation and probably also beyond this audience. We just observed a minor woe with Sphinx' linkchecker we wanted to share with you. We really like that the linkchecker is able to check anchors within HTML documents as contributed by on behalf of . With kind regards, Andreas. Describe the bug We had the link [1] in our documentation, and, maybe after upgrading to more recent versions of Sphinx, the linkchecker suddenly started croaking on that. After changing it to [2], it worked again. When inspecting the source code of the respective HTML page, you can clearly see that the anchor name defined by is technically correct. However, it apparently has worked before by referencing . So, we are wondering if something changed on GitHub's reStructuredText renderer or even Browsers interpreting the HTML link anchors differently. When invoking those links [1,2] in the Browser, actually both work, including navigation to the appropriate place within the page. Funny, hm? [1] [2] Expected behavior Technically, from the perspective we know our way around HTML, the behavior is probably the right thing and correct. However, as we can see, something might have been changed on the HTML standard that Browsers are capable of interpreting different styles of defining link anchors. So, it might be worth to revisit this space and maybe improve the linkchecker implementation on those aspects. Environment info OS: Linux Python version: 3.9.2 Sphinx version: 3.5.2 Firefox: 86.0


Question: if 'refuri' not in refnode:                 continue             uri = refnode['refuri']<ins>            newuri = self.app.emit_firstresult('linkcheck-process-uri', uri)            if newuri:                uri = newuri</ins>             lineno = get_node_line(refnode)             uri_info = Hyperlink(uri, self.env.docname, lineno)             if uri not in hyperlinks:


Question: for imgnode in self.document.traverse(nodes.image):             uri = imgnode['candidates'].get('?')             if uri and '://' in uri:                newuri = self.app.emit_firstresult('linkcheck-process-uri', uri)                if newuri:                    uri = newuri                 lineno = get_node_line(imgnode)                 uri_info = Hyperlink(uri, self.env.docname, lineno)                 if uri not in hyperlinks:                     hyperlinks[uri] = uri_info  def rewrite_github_anchor(app: Sphinx, uri: str) -> Optional[str]:    """Rewrite anchor name of the hyperlink to github.com    The hyperlink anchors in github.com are dynamically generated.  This rewrites    them before checking and makes them comparable.    """    parsed = urlparse(uri)    if parsed.hostname == "github.com" and parsed.fragment:        prefixed = parsed.fragment.startswith('user-content-')        if not prefixed:            fragment = f'user-content-{parsed.fragment}'            return urlunparse(parsed._replace(fragment=fragment))    return None def setup(app: Sphinx) -> Dict[str, Any]:     app.add_builder(CheckExternalLinksBuilder)     app.add_post_transform(HyperlinkCollector)


Question: app.add_config_value('linkcheck_anchors_ignore', ["^!"], None)     app.add_config_value('linkcheck_rate_limit_timeout', 300.0, None) <ins>    app.add_event('linkcheck-process-uri')    app.connect('linkcheck-process-uri', rewrite_github_anchor)</ins>     return {         'version': 'builtin',         'parallel_read_safe': True,


Question: __init__ = self.get_attr(self.object, '__init__', None)             initdocstring = getdoc(__init__, self.get_attr,                                    self.config.autodoc_inherit_docstrings,                                   self.parent, self.object_name)                                   self.object, '__init__')             # for new-style classes, no __init__ means default __init__             if (initdocstring is not None and                 (initdocstring == object.__init__.__doc__ or  # for pypy

Building autodocs for derived classes produces invalid warnings Describe the bug Generating API docs using the autodoc extensions, when a base class has doc strings in the constructor as well as in the class definition, and the autoclasscontent feature is set to "both", produces superfluous warnings when parsing derived classes with overloaded constructors with no doc strings on them. To Reproduce a base class with a constructor, and provide both a class doc string and a constructor doc string, something like this: a derived class that has an overloaded constructor, with no doc string, something like this: the auto content option in Sphinx to combine the class doc string and constructor doc strings together (ie: in the script) to generate the docs using the apidoc extension (ie: when using , just run ) Expected behavior Expected behavior: the API docs for both the base and derived classes should be generated without warnings. Actual behavior: docs for the base class generate correctly, but docs for the derived class produce the following warning "docstring of sample.MyDerived: WARNING: Unexpected indentation." Environment info OS: MacOS 11.3.1 (reproducible on several other Linux / Mac systems) Python version: 3.6.8 (reproducible on several newer versions as well) Sphinx version: 4.0.1 (reproducible on versions as far back as v2.2.0) Sphinx extensions: , Extra tools: (optional)


Question: __new__ = self.get_attr(self.object, '__new__', None)                 initdocstring = getdoc(__new__, self.get_attr,                                        self.config.autodoc_inherit_docstrings,                                       self.parent, self.object_name)                                       self.object, '__new__')                 # for new-style classes, no __new__ means default __new__                 if (initdocstring is not None and                     (initdocstring == object.__new__.__doc__ or  # for pypydiff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py-- a/sphinx/util/inspect.py++ b/sphinx/util/inspect.py


Question: if ispartial(obj) and doc == obj.__class__.__doc__:         return getdoc(obj.func)     elif doc is None and allow_inherited:        doc = inspect.getdoc(obj)        if doc is None and cls and name:            # inspect.getdoc() does not support some kind of inherited and decorated methods.            # This tries to obtain the docstring from super classes.            for basecls in getattr(cls, '__mro__', []):        if cls and name:            # Check a docstring of the attribute or method from super classes.            for basecls in getmro(cls):                 meth = safe_getattr(basecls, name, None)                 if meth is not None:                    doc = inspect.getdoc(meth)                    if doc:                    doc = attrgetter(meth, '__doc__', None)                    if doc is not None:                         break             if doc is None:                # retry using `inspect.getdoc()`                for basecls in getmro(cls):                    meth = safe_getattr(basecls, name, None)                    if meth is not None:                        doc = inspect.getdoc(meth)                        if doc is not None:                            break        if doc is None:            doc = inspect.getdoc(obj)     return doc


Question: (object_description(key), object_description(object[key]))                      for key in sorted_keys)             return "{%s}" % ", ".join(items)    if isinstance(object, set):    elif isinstance(object, set):         try:             sorted_values = sorted(object)         except TypeError:             pass  # Cannot sort set values, fall back to generic repr         else:             return "{%s}" % ", ".join(object_description(x) for x in sorted_values)    if isinstance(object, frozenset):    elif isinstance(object, frozenset):         try:             sorted_values = sorted(object)         except TypeError:

Python Enum values (used to show default values in function signatures) are rendered ugly. Python Enum values (used to show default values in function signatures) are rendered ugly. To Reproduce I made a minimal example to show the issue: Expected behavior I would hope the signature rendered as: uglyenumfunc(e: uglyenum.MyEnum = MyEnum.ValueA) → None Unfortunately, it renders as: uglyenumfunc(e: ugly_enum.MyEnum = <MyEnum.ValueA: 10) → None Environment info Python version: 3.9.5 Sphinx version: 4.0.2 Sphinx extensions: autodoc


Question: else:             return "frozenset({%s})" % ", ".join(object_description(x)                                                  for x in sorted_values)<ins>    elif isinstance(object, enum.Enum):        return "%s.%s" % (object.__class__.__name__, object.name)</ins>     try:         s = repr(object)     except Exception as exc:


Question: for node in result.traverse(nodes.Text):                 node.parent[node.parent.index(node)] = nodes.Text(text)                 break<ins>        elif isinstance(result, pending_xref) and env.config.python_use_unqualified_type_names:            children = result.children            result.clear()            shortname = target.split('.')[-1]            textnode = innernode('', shortname)            contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),                         pending_xref_condition('', '', *children, condition='*')]            result.extend(contnodes)</ins>         return result      def make_xrefs(self, rolename: str, domain: str, target: str,

pythonuseunqualifiedtypenames does not work on function descriptions Describe the bug When combining the new configuration with , the type names are not shortened even though links are created. To Reproduce Here's a minimal project using Sphinx 4.0.2 on Python 3.9: This produces documentation looking like this: ! Expected behavior I'd expect the link to be shortened to like it is when .


Question: from docutils import nodes from docutils.nodes import Element from requests import Responsefrom requests.exceptions import HTTPError, TooManyRedirectsfrom requests.exceptions import ConnectionError, HTTPError, TooManyRedirects  from sphinx.application import Sphinx from sphinx.builders.dummy import DummyBuilder

Linkcheck Reports Broken Link when Remote Server Closes on HEAD Request Describe the bug Running on a document that contains an external link to a website may report the link is broken when a web browser may successfully open the link. Specifically, if the website closes its connection when receiving the request method, then will receive a exception, which bypasses the logic that would otherwise have it make an request. A specific example of a website exhibiting this behaviour is To Reproduce Steps to reproduce the behaviour: Observe linkcheck reporting a broken link: Open in your browser Click "a link to the US Patent Website" Observe the link opening and rendering normally Expected behavior If a link is valid, and the website is returning valid content for an request, should not report the link as . Internally, in , if a call to raises a exception, it should attempt a just like it does with and . Your project Environment info OS: (but this does not appear to be OS-dependent) Python version: and Sphinx version: and Sphinx extensions: none Extra tools: any common web browser Additional context Using , we can see that this particular website closes connections when receiving : Observe that this returns the expected HTML content Observe that this fails to return any content from the server


Question: config=self.config, auth=auth_info,                                                  **kwargs)                         response.raise_for_status()                    except (HTTPError, TooManyRedirects) as err:                    # Servers drop the connection on HEAD requests, causing                    # ConnectionError.                    except (ConnectionError, HTTPError, TooManyRedirects) as err:                         if isinstance(err, HTTPError) and err.response.status_code == 429:                             raise                         # retry with GET request if that fails, some servers


Question: return x  <ins>def is_path_or_empty(x: str) -> str:    if x == '':        return x    return is_path(x)</ins> def allow_empty(x: str) -> str:     return x

with existing doesn't exit easily Describe the bug I've attached a screenshot in the screenshots section which I think explains the bug better. I'm running in a folder with a already existing. It says "Please enter a new root path name (or just Enter to exit)". However, upon pressing 'Enter' it returns an error message "Please enter a valid path name". To Reproduce Steps to reproduce the behavior: Expected behavior After pressing Enter, sphinx-quickstart exits. Your project n/a Screenshots ! I press Enter for the first prompt. Environment info OS: Ubuntu 20.04 Python version: Python 3.8.5 Sphinx version: sphinx-build 3.2.1 Sphinx extensions: none Extra tools: none Additional context I had a quick search but couldn't find any similar existing issues. Sorry if this is a duplicate.


Question: print(__('sphinx-quickstart will not overwrite existing Sphinx projects.'))         print()         d['path'] = do_prompt(__('Please enter a new root path (or just Enter to exit)'),                              '', is_path)                              '', is_path_or_empty)         if not d['path']:             sys.exit(1)


Question: for node in self.document.traverse(matcher):  # type: TextElement             if any(matcher(subnode) for subnode in node):                 pos = node.parent.index(node)                for subnode in reversed(node[1:]):                for subnode in reversed(list(node)):                     node.remove(subnode)                     if matcher(subnode):                         node.parent.insert(pos + 1, subnode)                     else:                         newnode = node.__class__('', '', subnode, **node.attributes)                         node.parent.insert(pos + 1, newnode)                # move node if all children became siblings of the node                if not len(node):                    node.parent.remove(node)   class ManualPageTranslator(SphinxTranslator, BaseTranslator):

Man page using :samp: with braces - font doesn't reset There are issues with the man page rendering when using :samp: The font change doesn't reset after the closure of the samp but runs onto subsequent lines until certain 'resetting' elements are encountered, such as a bullet, heading and probably others I haven't tested for. :samp: is ok. HTML and Latex (pdf) formats are also unaffected by this issue. MWE included with screen shot. Text that should be plain. A bullet that should be plain. And back to normal. :samp: More text that should be plain. A bullet that should be plain. And back to normal. :samp: All ok on this line. !


Question: return "%s %s" % (self.visit(node.op), self.visit(node.operand))      def visit_Tuple(self, node: ast.Tuple) -> str:        if node.elts:            return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"        else:        if len(node.elts) == 0:             return "()"        elif len(node.elts) == 1:            return "(%s,)" % self.visit(node.elts[0])        else:            return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"      if sys.version_info < (3, 8):         # these ast nodes were deprecated in python 3.8

1-element tuple rendered incorrectly Describe the bug This is a followup to which has been addressed in . However the special case of a 1-element tuple is still not handled correctly. is rendered as , but should keep the trailing comma. To Reproduce Add a testcase at


Question: self.add_line('   :annotation: %s' % self.options.annotation,                           sourcename)         else:            # obtain annotation for this data            annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)            if self.objpath[-1] in annotations:                objrepr = stringify_typehint(annotations.get(self.objpath[-1]))                self.add_line('   :type: ' + objrepr, sourcename)            if self.config.autodoc_typehints != 'none':                # obtain annotation for this data                annotations = get_type_hints(self.parent, None,                                             self.config.autodoc_type_aliases)                if self.objpath[-1] in annotations:                    objrepr = stringify_typehint(annotations.get(self.objpath[-1]))                    self.add_line('   :type: ' + objrepr, sourcename)              try:                 if self.options.no_value or self.should_suppress_value_header():

Autodoc still using typehint types in properties Describe the bug Despite being set to , properties have type hints rendered in the output. To Reproduce Steps to reproduce the behavior. Download the zip below and go to then run Expected behavior No type hints in the output. Your project Screenshots ! Environment info OS: Windows 10 Python version: 3.9.2 Sphinx version: 4.0.2 Sphinx extensions: Extra tools: N/A Additional context Issue discovered in a real project (edit: I removed it using CSS).


Question: elif self.options.annotation:             self.add_line('   :annotation: %s' % self.options.annotation, sourcename)         else:            # obtain type annotation for this attribute            annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases)            if self.objpath[-1] in annotations:                objrepr = stringify_typehint(annotations.get(self.objpath[-1]))                self.add_line('   :type: ' + objrepr, sourcename)            if self.config.autodoc_typehints != 'none':                # obtain type annotation for this attribute                annotations = get_type_hints(self.parent, None,                                             self.config.autodoc_type_aliases)                if self.objpath[-1] in annotations:                    objrepr = stringify_typehint(annotations.get(self.objpath[-1]))                    self.add_line('   :type: ' + objrepr, sourcename)              try:                 if self.options.no_value or self.should_suppress_value_header():


Question: if inspect.isabstractmethod(self.object):             self.add_line('   :abstractmethod:', sourcename)         if safe_getattr(self.object, 'fget', None):        if safe_getattr(self.object, 'fget', None) and self.config.autodoc_typehints != 'none':             try:                 signature = inspect.signature(self.object.fget,                                               type_aliases=self.config.autodoc_type_aliases)


Question: import sys import time from os import pathfrom typing import IO, Any, Dict, List, Tuplefrom typing import IO, Any, Dict, List, Optional, Tuple from urllib.parse import urlsplit, urlunsplit  from docutils import nodesfrom docutils.nodes import TextElementfrom docutils.nodes import Element, TextElement from docutils.utils import relative_path  import sphinx

Add an option to not use intersphinx references as a fallback That intersphinx references are used as fallbacks if a reference is not available in a project recently caused us a problem. We have a site () which is the latest build of . We use intersphinx as follows, so we can link to the latest documentation in some sections: Our docs included a label () so this reference was available on . This label was removed on a branch, along with one of the two references to this label. In this case we would expect that building the branch would fail, as there is a reference to a label which doesn't exist. Unexpectedly, the branch built successfully, because the label was found in as a fall back when it was not found locally. The branch was merged, and then later, when changed (because was built again) builds stopped working because there was a reference to a non-existent label. It would be good to have an option to not fall back, maybe something like .


Question: from sphinx.application import Sphinx from sphinx.builders.html import INVENTORY_FILENAME from sphinx.config import Configfrom sphinx.domains import Domain from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.util import logging, requests from sphinx.util.inventory import InventoryFilefrom sphinx.util.typing import Inventoryfrom sphinx.util.typing import Inventory, InventoryItem  logger = logging.getLogger(__name__)


Question: inventories.main_inventory.setdefault(type, {}).update(objects)  def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,                      contnode: TextElement) -> nodes.reference:    """Attempt to resolve a missing reference via intersphinx references."""    target = node['reftarget']    inventories = InventoryAdapter(env)    objtypes: List[str] = None    if node['reftype'] == 'any':        # we search anything!        objtypes = ['%s:%s' % (domain.name, objtype)                    for domain in env.domains.values()                    for objtype in domain.object_types]        domain = Nonedef _create_element_from_result(domain: Domain, inv_name: Optional[str],                                data: InventoryItem,                                node: pending_xref, contnode: TextElement) -> Element:    proj, version, uri, dispname = data    if '://' not in uri and node.get('refdoc'):        # get correct path in case of subdirectories        uri = path.join(relative_path(node['refdoc'], '.'), uri)    if version:        reftitle = _('(in %s v%s)') % (proj, version)     else:        domain = node.get('refdomain')        if not domain:        reftitle = _('(in %s)') % (proj,)    newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle)    if node.get('refexplicit'):        # use whatever title was given        newnode.append(contnode)    elif dispname == '-' or \            (domain.name == 'std' and node['reftype'] == 'keyword'):        # use whatever title was given, but strip prefix        title = contnode.astext()        if inv_name is not None and title.startswith(inv_name + ':'):            newnode.append(contnode.__class__(title[len(inv_name) + 1:],                                              title[len(inv_name) + 1:]))        else:            newnode.append(contnode)    else:        # else use the given display name (used for :ref:)        newnode.append(contnode.__class__(dispname, dispname))    return newnodedef _resolve_reference_in_domain_by_target(        inv_name: Optional[str], inventory: Inventory,        domain: Domain, objtypes: List[str],        target: str,        node: pending_xref, contnode: TextElement) -> Optional[Element]:    for objtype in objtypes:        if objtype not in inventory:            # Continue if there's nothing of this kind in the inventory            continue        if target in inventory[objtype]:            # Case sensitive match, use it            data = inventory[objtype][target]        elif objtype == 'std:term':            # Check for potential case insensitive matches for terms only            target_lower = target.lower()            insensitive_matches = list(filter(lambda k: k.lower() == target_lower,                                              inventory[objtype].keys()))            if insensitive_matches:                data = inventory[objtype][insensitive_matches[0]]            else:                # No case insensitive match either, continue to the next candidate                continue        else:            # Could reach here if we're not a term but have a case insensitive match.            # This is a fix for terms specifically, but potentially should apply to            # other types.            continue        return _create_element_from_result(domain, inv_name, data, node, contnode)    return Nonedef _resolve_reference_in_domain(env: BuildEnvironment,                                 inv_name: Optional[str], inventory: Inventory,                                 honor_disabled_refs: bool,                                 domain: Domain, objtypes: List[str],                                 node: pending_xref, contnode: TextElement                                 ) -> Optional[Element]:    # we adjust the object types for backwards compatibility    if domain.name == 'std' and 'cmdoption' in objtypes:        # until Sphinx-1.6, cmdoptions are stored as std:option        objtypes.append('option')    if domain.name == 'py' and 'attribute' in objtypes:        # Since Sphinx-2.1, properties are stored as py:method        objtypes.append('method')    # the inventory contains domain:type as objtype    objtypes = ["{}:{}".format(domain.name, t) for t in objtypes]    # now that the objtypes list is complete we can remove the disabled ones    if honor_disabled_refs:        disabled = env.config.intersphinx_disabled_reftypes        objtypes = [o for o in objtypes if o not in disabled]    # without qualification    res = _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,                                                 node['reftarget'], node, contnode)    if res is not None:        return res    # try with qualification of the current scope instead    full_qualified_name = domain.get_full_qualified_name(node)    if full_qualified_name is None:        return None    return _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,                                                  full_qualified_name, node, contnode)def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory,                       honor_disabled_refs: bool,                       node: pending_xref, contnode: TextElement) -> Optional[Element]:    # disabling should only be done if no inventory is given    honor_disabled_refs = honor_disabled_refs and inv_name is None    if honor_disabled_refs and '*' in env.config.intersphinx_disabled_reftypes:        return None    typ = node['reftype']    if typ == 'any':        for domain_name, domain in env.domains.items():            if honor_disabled_refs \                    and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes:                continue            objtypes = list(domain.object_types)            res = _resolve_reference_in_domain(env, inv_name, inventory,                                               honor_disabled_refs,                                               domain, objtypes,                                               node, contnode)            if res is not None:                return res        return None    else:        domain_name = node.get('refdomain')        if not domain_name:             # only objects in domains are in the inventory             return None        objtypes = env.get_domain(domain).objtypes_for_role(node['reftype'])        if honor_disabled_refs \                and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes:            return None        domain = env.get_domain(domain_name)        objtypes = domain.objtypes_for_role(typ)         if not objtypes:             return None        objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes]    if 'std:cmdoption' in objtypes:        # until Sphinx-1.6, cmdoptions are stored as std:option        objtypes.append('std:option')    if 'py:attribute' in objtypes:        # Since Sphinx-2.1, properties are stored as py:method        objtypes.append('py:method')    to_try = [(inventories.main_inventory, target)]    if domain:        full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)        if full_qualified_name:            to_try.append((inventories.main_inventory, full_qualified_name))    in_set = None    if ':' in target:        # first part may be the foreign doc set name        setname, newtarget = target.split(':', 1)        if setname in inventories.named_inventory:            in_set = setname            to_try.append((inventories.named_inventory[setname], newtarget))            if domain:                node['reftarget'] = newtarget                full_qualified_name = env.get_domain(domain).get_full_qualified_name(node)                if full_qualified_name:                    to_try.append((inventories.named_inventory[setname], full_qualified_name))    for inventory, target in to_try:        for objtype in objtypes:            if objtype not in inventory:                # Continue if there's nothing of this kind in the inventory                continue            if target in inventory[objtype]:                # Case sensitive match, use it                proj, version, uri, dispname = inventory[objtype][target]            elif objtype == 'std:term':                # Check for potential case insensitive matches for terms only                target_lower = target.lower()                insensitive_matches = list(filter(lambda k: k.lower() == target_lower,                                                  inventory[objtype].keys()))                if insensitive_matches:                    proj, version, uri, dispname = inventory[objtype][insensitive_matches[0]]                else:                    # No case insensitive match either, continue to the next candidate                    continue            else:                # Could reach here if we're not a term but have a case insensitive match.                # This is a fix for terms specifically, but potentially should apply to                # other types.                continue        return _resolve_reference_in_domain(env, inv_name, inventory,                                            honor_disabled_refs,                                            domain, objtypes,                                            node, contnode)             if '://' not in uri and node.get('refdoc'):                # get correct path in case of subdirectories                uri = path.join(relative_path(node['refdoc'], '.'), uri)            if version:                reftitle = _('(in %s v%s)') % (proj, version)            else:                reftitle = _('(in %s)') % (proj,)            newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle)            if node.get('refexplicit'):                # use whatever title was given                newnode.append(contnode)            elif dispname == '-' or \                    (domain == 'std' and node['reftype'] == 'keyword'):                # use whatever title was given, but strip prefix                title = contnode.astext()                if in_set and title.startswith(in_set + ':'):                    newnode.append(contnode.__class__(title[len(in_set) + 1:],                                                      title[len(in_set) + 1:]))                else:                    newnode.append(contnode)            else:                # else use the given display name (used for :ref:)                newnode.append(contnode.__class__(dispname, dispname))            return newnode    # at least get rid of the ':' in the target if no explicit title given    if in_set is not None and not node.get('refexplicit', True):        if len(contnode) and isinstance(contnode[0], nodes.Text):            contnode[0] = nodes.Text(newtarget, contnode[0].rawsource)     return Nonedef inventory_exists(env: BuildEnvironment, inv_name: str) -> bool:    return inv_name in InventoryAdapter(env).named_inventorydef resolve_reference_in_inventory(env: BuildEnvironment,                                   inv_name: str,                                   node: pending_xref, contnode: TextElement                                   ) -> Optional[Element]:    """Attempt to resolve a missing reference via intersphinx references.    Resolution is tried in the given inventory with the target as is.    Requires ``inventory_exists(env, inv_name)``.    """    assert inventory_exists(env, inv_name)    return _resolve_reference(env, inv_name, InventoryAdapter(env).named_inventory[inv_name],                              False, node, contnode)def resolve_reference_any_inventory(env: BuildEnvironment,                                    honor_disabled_refs: bool,                                    node: pending_xref, contnode: TextElement                                    ) -> Optional[Element]:    """Attempt to resolve a missing reference via intersphinx references.    Resolution is tried with the target as is in any inventory.    """    return _resolve_reference(env, None, InventoryAdapter(env).main_inventory,                              honor_disabled_refs,                              node, contnode)def resolve_reference_detect_inventory(env: BuildEnvironment,                                       node: pending_xref, contnode: TextElement                                       ) -> Optional[Element]:    """Attempt to resolve a missing reference via intersphinx references.    Resolution is tried first with the target as is in any inventory.    If this does not succeed, then the target is split by the first ``:``,    to form ``inv_name:newtarget``. If ``inv_name`` is a named inventory, then resolution    is tried in that inventory with the new target.    """    # ordinary direct lookup, use data as is    res = resolve_reference_any_inventory(env, True, node, contnode)    if res is not None:        return res    # try splitting the target into 'inv_name:target'    target = node['reftarget']    if ':' not in target:        return None    inv_name, newtarget = target.split(':', 1)    if not inventory_exists(env, inv_name):        return None    node['reftarget'] = newtarget    res_inv = resolve_reference_in_inventory(env, inv_name, node, contnode)    node['reftarget'] = target    return res_invdef missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,                      contnode: TextElement) -> Optional[Element]:    """Attempt to resolve a missing reference via intersphinx references."""    return resolve_reference_detect_inventory(env, node, contnode)   def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:


Question: app.add_config_value('intersphinx_mapping', {}, True)     app.add_config_value('intersphinx_cache_limit', 5, False)     app.add_config_value('intersphinx_timeout', None, False)    app.add_config_value('intersphinx_disabled_reftypes', [], True)     app.connect('config-inited', normalize_intersphinx_mapping, priority=800)     app.connect('builder-inited', load_mappings)     app.connect('missing-reference', missing_reference)diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py-- a/sphinx/util/typing.py++ b/sphinx/util/typing.py


Question: TitleGetter = Callable[[nodes.Node], str]  # inventory data on memoryInventory = Dict[str, Dict[str, Tuple[str, str, str, str]]]InventoryItem = Tuple[str, str, str, str]Inventory = Dict[str, Dict[str, InventoryItem]]   def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dict[str, Any]:


Question: option_spec = PyObject.option_spec.copy()     option_spec.update({         'abstractmethod': directives.flag,<ins>        'classmethod': directives.flag,</ins>         'type': directives.unchanged,     })

Methods decorated with and do not get documented. EDIT: The problem seems to be that returns , thus sphinx can just lookup . However, returns the type of the returned object, since essentially, a ends up behaving like a class attribute. So Sphinx doesn't really have a chance to extract the docstring. EDIT 2: Seems like this will get fixed in python 3.10, cf. I will try to test this with the beta release. That is, since python version 3.9 we can write code like However, sphinx does not seem to document any such methods (regular decorated methods get documented just fine.) The following methods were erroneously not documented: - - - Methods that are decorated with both and should be documented appropriately. No response Ubuntu 20.04.2 LTS 6 3 , No response No response


Question: return fullname, prefix      def get_signature_prefix(self, sig: str) -> str:        prefix = ['property']        prefix = []         if 'abstractmethod' in self.options:            prefix.insert(0, 'abstract')            prefix.append('abstract')        if 'classmethod' in self.options:            prefix.append('class')         prefix.append('property')         return ' '.join(prefix) + ' '      def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py-- a/sphinx/ext/autodoc/__init__.py++ b/sphinx/ext/autodoc/__init__.py


Question: isattr = False              doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,                         self.parent, self.object_name)                         self.object, membername)             if not isinstance(doc, str):                 # Ignore non-string __doc__                 doc = None


Question: @classmethod     def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any                             ) -> bool:        return inspect.isproperty(member) and isinstance(parent, ClassDocumenter)        if isinstance(parent, ClassDocumenter):            if inspect.isproperty(member):                return True            else:                __dict__ = safe_getattr(parent.object, '__dict__', {})                obj = __dict__.get(membername)                return isinstance(obj, classmethod) and inspect.isproperty(obj.__func__)        else:            return False    def import_object(self, raiseerror: bool = False) -> bool:        """Check the exisitence of uninitialized instance attribute when failed to import        the attribute."""        ret = super().import_object(raiseerror)        if ret and not inspect.isproperty(self.object):            __dict__ = safe_getattr(self.parent, '__dict__', {})            obj = __dict__.get(self.objpath[-1])            if isinstance(obj, classmethod) and inspect.isproperty(obj.__func__):                self.object = obj.__func__                self.isclassmethod = True                return True            else:                return False        self.isclassmethod = False        return ret      def document_members(self, all_members: bool = False) -> None:         pass


Question: sourcename = self.get_sourcename()         if inspect.isabstractmethod(self.object):             self.add_line('   :abstractmethod:', sourcename)        if self.isclassmethod:            self.add_line('   :classmethod:', sourcename)          if safe_getattr(self.object, 'fget', None) and self.config.autodoc_typehints != 'none':             try:diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py-- a/sphinx/util/inspect.py++ b/sphinx/util/inspect.py


Question: return isinstance(obj, (partial, partialmethod))  def isclassmethod(obj: Any) -> bool:def isclassmethod(obj: Any, cls: Any = None, name: str = None) -> bool:     """Check if the object is classmethod."""     if isinstance(obj, classmethod):         return True     elif inspect.ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__):         return True    elif cls and name:        for basecls in getmro(cls):            meth = basecls.__dict__.get(name)            if meth:                return isclassmethod(meth)      return False


Question: * inherited docstring     * inherited decorated methods     """<ins>    if cls and name and isclassmethod(obj, cls, name):        for basecls in getmro(cls):            meth = basecls.__dict__.get(name)            if meth:                return getdoc(meth.__func__)</ins>     doc = attrgetter(obj, '__doc__', None)     if ispartial(obj) and doc == obj.__class__.__doc__:         return getdoc(obj.func)


Question: return INVALID_BUILTIN_CLASSES[annotation]     elif (getattr(annotation, '__module__', None) == 'builtins' and           hasattr(annotation, '__qualname__')):        return annotation.__qualname__        if hasattr(annotation, '__args__'):  # PEP 585 generic            return repr(annotation)        else:            return annotation.__qualname__     elif annotation is Ellipsis:         return '...'

PEP 585 type hints not rendered correctly If you use a PEP 585 generic as an annotation i.e. , autodoc renders the annotation as rather than , this behaviour differs from using which renders as expected. Fixing this is quite simple as far as I can tell, just needs to check if the annotation has and if it does, return An annotation of to be rendered as ! Mac Python 3.9.3 1 autodoc No response No response


Question: app.add_event('linkcheck-process-uri')      app.connect('config-inited', compile_linkcheck_allowed_redirects, priority=800)    app.connect('linkcheck-process-uri', rewrite_github_anchor)    # FIXME: Disable URL rewrite handler for github.com temporarily.    # ref: https://github.com/sphinx-doc/sphinx/issues/9435    # app.connect('linkcheck-process-uri', rewrite_github_anchor)      return {         'version': 'builtin',

github linkcheck anchor change in 4.1.0 break some usage Given a link like: in a github doc, with release 4.1.0 this will fail with linkcheck, while previously it worked. It passes. No response Linux 5 0 No response No response The relevant change happened in Failing test logs:


Question: _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"                         r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) <ins>_simple_type_sepcifiers_re = re.compile(r"""(?x)    \b(    void|_Bool|bool    # Integer    # -------    |((signed|unsigned)\s+)?(char|(        ((long\s+long|long|short)\s+)?int    ))    |__uint128|__int128    # extensions    |((signed|unsigned)\s+)?__int(8|16|32|64|128)    # Floating-point    # --------------    |(float|double|long\s+double)(\s+(_Complex|complex|_Imaginary|imaginary))?    |(_Complex|complex|_Imaginary|imaginary)\s+(float|double|long\s+double)    |_Decimal(32|64|128)    # extensions    |__float80|_Float64x|__float128|_Float128|__ibm128    |__fp16    # Fixed-point, extension    |(_Sat\s+)?((signed|unsigned)\s+)?((short|long|long\s+long)\s+)?(_Fract|fract|_Accum|accum)    # Integer types that could be prefixes of the previous ones    # ---------------------------------------------------------    |((signed|unsigned)\s+)?(long\s+long|long|short)    |signed|unsigned    )\b""")</ins>  class _DuplicateSymbolError(Exception):     def __init__(self, symbol: "Symbol", declaration: "ASTDeclaration") -> None:

Missing support for GNU extension types like or I'm currently working on the transition of the GNU C compiler (GCC) manuals and I noticed there are unsupported C extensions like: where I see the following parsing error: Right now, there's some special casing for e.g. 'unsigned' type: One possible fix is adding the mentioned C extension handling for the following types: or I can see a domain parser can become public via an API entry point: What do you think?


Question: class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):     def __init__(self, name: str) -> None:        self.name = name        self.names = name.split()      def _stringify(self, transform: StringifyTransform) -> str:        return self.name        return ' '.join(self.names)      def describe_signature(self, signode: TextElement, mode: str,                            env: "BuildEnvironment", symbol: "Symbol") -> None:        signode += addnodes.desc_sig_keyword_type(self.name, self.name)        first = True        for n in self.names:            if not first:                signode += addnodes.desc_sig_space()            else:                first = False            signode += addnodes.desc_sig_keyword_type(n, n)   class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):


Question: class DefinitionParser(BaseParser):<del>    # those without signedness and size modifiers    # see https://en.cppreference.com/w/cpp/language/types    _simple_fundamental_types = (        'void', '_Bool', 'bool', 'char', 'int', 'float', 'double',        '__int64',    )    _prefix_keys = ('struct', 'enum', 'union')</del>     @property     def language(self) -> str:         return 'C'


Question: return ASTNestedName(names, rooted)      def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:        # fundamental types        # fundamental types, https://en.cppreference.com/w/c/language/type        # and extensions         self.skip_ws()        for t in self._simple_fundamental_types:            if self.skip_word(t):                return ASTTrailingTypeSpecFundamental(t)        # TODO: this could/should be more strict        elements = []        if self.skip_word_and_ws('signed'):            elements.append('signed')        elif self.skip_word_and_ws('unsigned'):            elements.append('unsigned')        while 1:            if self.skip_word_and_ws('short'):                elements.append('short')            elif self.skip_word_and_ws('long'):                elements.append('long')            else:                break        if self.skip_word_and_ws('char'):            elements.append('char')        elif self.skip_word_and_ws('int'):            elements.append('int')        elif self.skip_word_and_ws('double'):            elements.append('double')        elif self.skip_word_and_ws('__int64'):            elements.append('__int64')        if len(elements) > 0:            return ASTTrailingTypeSpecFundamental(' '.join(elements))        if self.match(_simple_type_sepcifiers_re):            return ASTTrailingTypeSpecFundamental(self.matched_text)          # prefixed         prefix = None         self.skip_ws()        for k in self._prefix_keys:        for k in ('struct', 'enum', 'union'):             if self.skip_word_and_ws(k):                 prefix = k                 breakdiff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py-- a/sphinx/domains/cpp.py++ b/sphinx/domains/cpp.py


Question: 'while', 'xor', 'xor_eq' ] <ins>_simple_type_sepcifiers_re = re.compile(r"""(?x)    \b(    auto|void|bool    # Integer    # -------    |((signed|unsigned)\s+)?(char|__int128|(        ((long\s+long|long|short)\s+)?int    ))    |wchar_t|char(8|16|32)_t    # extensions    |((signed|unsigned)\s+)?__int(64|128)    # Floating-point    # --------------    |(float|double|long\s+double)(\s+(_Complex|_Imaginary))?    |(_Complex|_Imaginary)\s+(float|double|long\s+double)    # extensions    |__float80|_Float64x|__float128|_Float128    # Integer types that could be prefixes of the previous ones    # ---------------------------------------------------------    |((signed|unsigned)\s+)?(long\s+long|long|short)    |signed|unsigned    )\b""")</ins> _max_id = 4 _id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4'] # Ids are used in lookup keys which are used across pickled files,


Question: 'long long int': 'x',     'signed long long': 'x',     'signed long long int': 'x',    '__int64': 'x',     'unsigned long long': 'y',     'unsigned long long int': 'y',    '__int128': 'n',    'signed __int128': 'n',    'unsigned __int128': 'o',     'float': 'f',     'double': 'd',     'long double': 'e',    '__float80': 'e', '_Float64x': 'e',    '__float128': 'g', '_Float128': 'g',    'float _Complex': 'Cf', '_Complex float': 'Cf',    'double _Complex': 'Cd', '_Complex double': 'Cd',    'long double _Complex': 'Ce', '_Complex long double': 'Ce',    'float _Imaginary': 'f', '_Imaginary float': 'f',    'double _Imaginary': 'd', '_Imaginary double': 'd',    'long double _Imaginary': 'e', '_Imaginary long double': 'e',     'auto': 'Da',     'decltype(auto)': 'Dc',     'std::nullptr_t': 'Dn'


Question: class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):     def __init__(self, name: str) -> None:        self.name = name        self.names = name.split()      def _stringify(self, transform: StringifyTransform) -> str:        return self.name        return ' '.join(self.names)      def get_id(self, version: int) -> str:         if version == 1:             res = []            for a in self.name.split(' '):            for a in self.names:                 if a in _id_fundamental_v1:                     res.append(_id_fundamental_v1[a])                 else:                     res.append(a)             return '-'.join(res)         if self.name not in _id_fundamental_v2:        txt = str(self)        if txt not in _id_fundamental_v2:             raise Exception(                 'Semi-internal error: Fundamental type "%s" can not be mapped '                'to an id. Is it a true fundamental type? If not so, the '                'parser should have rejected it.' % self.name)        return _id_fundamental_v2[self.name]                'to an ID. Is it a true fundamental type? If not so, the '                'parser should have rejected it.' % txt)        return _id_fundamental_v2[txt]      def describe_signature(self, signode: TextElement, mode: str,                            env: "BuildEnvironment", symbol: "Symbol") -> None:        signode += addnodes.desc_sig_keyword_type(self.name, self.name)        first = True        for n in self.names:            if not first:                signode += addnodes.desc_sig_space()            else:                first = False            signode += addnodes.desc_sig_keyword_type(n, n)   class ASTTrailingTypeSpecDecltypeAuto(ASTTrailingTypeSpec):


Question: class DefinitionParser(BaseParser):<del>    # those without signedness and size modifiers    # see https://en.cppreference.com/w/cpp/language/types    _simple_fundemental_types = (        'void', 'bool', 'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',        'int', 'float', 'double', 'auto'    )    _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')</del>     @property     def language(self) -> str:         return 'C++'


Question: # ==========================================================================      def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:        # fundemental types        # fundamental types, https://en.cppreference.com/w/cpp/language/type        # and extensions         self.skip_ws()        for t in self._simple_fundemental_types:            if self.skip_word(t):                return ASTTrailingTypeSpecFundamental(t)        # TODO: this could/should be more strict        elements = []        if self.skip_word_and_ws('signed'):            elements.append('signed')        elif self.skip_word_and_ws('unsigned'):            elements.append('unsigned')        while 1:            if self.skip_word_and_ws('short'):                elements.append('short')            elif self.skip_word_and_ws('long'):                elements.append('long')            else:                break        if self.skip_word_and_ws('char'):            elements.append('char')        elif self.skip_word_and_ws('int'):            elements.append('int')        elif self.skip_word_and_ws('double'):            elements.append('double')        if len(elements) > 0:            return ASTTrailingTypeSpecFundamental(' '.join(elements))        if self.match(_simple_type_sepcifiers_re):            return ASTTrailingTypeSpecFundamental(self.matched_text)          # decltype         self.skip_ws()


Question: # prefixed         prefix = None         self.skip_ws()        for k in self._prefix_keys:        for k in ('class', 'struct', 'enum', 'union', 'typename'):             if self.skip_word_and_ws(k):                 prefix = k                 break


Question: typ = self.options.get('type')         if typ:            signode += addnodes.desc_annotation(typ, ': ' + typ)            annotations = _parse_annotation(typ, self.env)            signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)          return fullname, prefix

Cross-references don't work in property's type annotations A documented type in property's type annotation does not get cross-referenced: I expected the documented type in property's type annotation to be cross-referenced. Here's a link to the generated docs: https://sphinx-issue- Windows 10, Ubuntu 18.04 7, 3.8, 3.9 2 No response No response


Question: if node.value is Ellipsis:                 return [addnodes.desc_sig_punctuation('', "...")]             else:                return [nodes.Text(node.value)]                return [nodes.Text(repr(node.value))]         elif isinstance(node, ast.Expr):             return unparse(node.value)         elif isinstance(node, ast.Index):

Nitpick flags Literal annotation values as missing py:class When a value is present in a type annotation as , sphinx will treat the value as a . With nitpick enabled, values like end up failing, because is not a class. This is a problem for builds which want to use to catch doc errors. Setup a simple function which uses Literal, then attempt to autodoc it. e.g. I've pushed an example to . Just run with available to see the failing build. (or whatever literal value) should be present in the type annotation but should not trigger the nitpick warning. No response Linux 8, 3.9 2 autodoc No response No response


Question: result.append(addnodes.desc_sig_punctuation('', '['))             result.extend(unparse(node.slice))             result.append(addnodes.desc_sig_punctuation('', ']'))<ins>            # Wrap the Text nodes inside brackets by literal node if the subscript is a Literal            if result[0] in ('Literal', 'typing.Literal'):                for i, subnode in enumerate(result[1:], start=1):                    if isinstance(subnode, nodes.Text):                        result[i] = nodes.literal('', '', subnode)</ins>             return result         elif isinstance(node, ast.Tuple):             if node.elts:


Question: tree = ast_parse(annotation)         result = unparse(tree)         for i, node in enumerate(result):            if isinstance(node, nodes.Text) and node.strip():            if isinstance(node, nodes.literal):                result[i] = node[0]            elif isinstance(node, nodes.Text) and node.strip():                 result[i] = type_to_xref(str(node), env)         return result     except SyntaxError:


Question: # add inheritance info, if wanted         if not self.doc_as_attr and self.options.show_inheritance:            if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__):            if inspect.getorigbases(self.object):                 # A subclass of generic types                 # refs: PEP-560 <https://www.python.org/dev/peps/pep-0560/>                 bases = list(self.object.__orig_bases__)diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py-- a/sphinx/util/inspect.py++ b/sphinx/util/inspect.py

Incorrect base class detection I have a chain of subclasses, like A -B -C -D. When I document class D, it lists the base class as A instead of C. I would expect the base class to be the direct super class. No response macOS 10.15.7, Linux (whatever RtD uses) 11 1, 4.0.2 No response I don't think this is the same as because I can reproduce it with 4.0.1 too. Could be related to . For a class like , even though the super class is , the base class is listed as .


Question: return tuple()  <ins>def getorigbases(obj: Any) -> Optional[Tuple[Any, ...]]:    """Get __orig_bases__ from *obj* safely."""    if not inspect.isclass(obj):        return None    # Get __orig_bases__ from obj.__dict__ to avoid accessing the parent's __orig_bases__.    # refs: https://github.com/sphinx-doc/sphinx/issues/9607    __dict__ = safe_getattr(obj, '__dict__', {})    __orig_bases__ = __dict__.get('__orig_bases__')    if isinstance(__orig_bases__, tuple) and len(__orig_bases__) > 0:        return __orig_bases__    else:        return None</ins> def getslots(obj: Any) -> Optional[Dict]:     """Get __slots__ attribute of the class as dict.


Question: """Used by autodoc_mock_imports."""      __display_name__ = '_MockObject'<ins>    __name__ = ''</ins>     __sphinx_mock__ = True     __sphinx_decorator_args__: Tuple[Any, ...] = ()

Inherited classes not correctly documented when mocked We're experiencing an issue when documenting classes that inherit mocked classes. However, classes which inherit other classes from our own package are ok. This issue appears to be dependent on the version: : Everything is OK. : Classes that inherit mocked classes are not documented. (see ). This is fixed in . : The previously missing classes are now documented, but there is a problem with the "Bases" section in the docs. Example: In the docs for in this readthedocs build https://seldon--, the base class is listed as "Bases: " instead of "Bases: ". The "Bases" section should report not . i.e. see https://seldon-- ! ! Ubuntu 18.04 (used by readthedocs/build:6.0) 11 ["", "", "", "", "", "", "", "", "", "sphinxautodoctypehints", "", "nbsphinx", "nbsphinxlink", "mystparser"] No response demo PR: readthedocs demo build: https://seldon--


Question: return super().__new__(cls)      def __init__(self, *args: Any, **kwargs: Any) -> None:        self.__qualname__ = ''        self.__qualname__ = self.__name__      def __len__(self) -> int:         return 0


Question: attributes: Any = None, decorator_args: Tuple = ()) -> Any:     attrs = {'__module__': module,              '__display_name__': module + '.' + name,<ins>             '__name__': name,</ins>              '__sphinx_decorator_args__': decorator_args}     attrs.update(attributes or {})


Question: return ':py:obj:`None`'         elif cls is Ellipsis:             return '...'<ins>        elif isinstance(cls, str):            return cls</ins>         elif cls in INVALID_BUILTIN_CLASSES:             return ':py:class:`%s`' % INVALID_BUILTIN_CLASSES[cls]         elif inspect.isNewType(cls):

python bases in packages - follow up on Is your feature request related to a problem? Please describe. Same problem as described in Describe the solution you'd like sphinx checks if the base class is documented somewhere. If yes, it inserts the reference to the corresponding page automatically. Describe alternatives you've considered As originally proposed in , allow to return a list of strings that will be appended to Additional context was marked as but that PR unfortunately doesn't solve the problem: While I now can return a different base class, I still can't control the text of the hyperlink for that class. With being allowed to return strings, one could just return something like telegram.TelegramObject` PS: maybe we can just reopen and continue discussion there …