mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
commit
e116613a3a
@ -1,8 +1,6 @@
|
||||
comment: false
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
enabled: no
|
||||
patch:
|
||||
default:
|
||||
enabled: no
|
||||
|
13
.travis.yml
13
.travis.yml
@ -1,6 +1,6 @@
|
||||
language: python
|
||||
sudo: false
|
||||
os: linux
|
||||
dist: xenial
|
||||
language: python
|
||||
cache: pip
|
||||
|
||||
env:
|
||||
@ -9,7 +9,7 @@ env:
|
||||
- SKIP_LATEX_BUILD=1
|
||||
- IS_PYTHON=true
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
- python: '3.6'
|
||||
env:
|
||||
@ -20,7 +20,9 @@ matrix:
|
||||
- python: '3.8'
|
||||
env:
|
||||
- TOXENV=du16
|
||||
- PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
||||
# Disable codecov temporarily
|
||||
# refs: https://github.com/sphinx-doc/sphinx/pull/7286#issuecomment-596617853
|
||||
# - PYTEST_ADDOPTS="--cov ./ --cov-append --cov-config setup.cfg"
|
||||
- python: 'nightly'
|
||||
env:
|
||||
- TOXENV=py39
|
||||
@ -34,8 +36,7 @@ matrix:
|
||||
env: TOXENV=flake8
|
||||
|
||||
- language: node_js
|
||||
node_js:
|
||||
- 10.7
|
||||
node_js: '10.7'
|
||||
env: IS_PYTHON=false
|
||||
services: xvfb
|
||||
|
||||
|
18
CHANGES
18
CHANGES
@ -52,18 +52,17 @@ Incompatible changes
|
||||
* Due to the scoping changes for :rst:dir:`productionlist` some uses of
|
||||
:rst:role:`token` must be modified to include the scope which was previously
|
||||
ignored.
|
||||
* #6903: js domain: Internal data structure has changed. Both objects and
|
||||
modules have node_id for cross reference
|
||||
* #6903: Internal data structure of Python, reST and standard domains have
|
||||
changed. The node_id is added to the index of objects and modules. Now they
|
||||
contains a pair of docname and node_id for cross reference.
|
||||
* #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links
|
||||
to ``.. js:function:: parseInt``
|
||||
* #6903: rst domain: Internal data structure has changed. Now objects have
|
||||
node_id for cross reference
|
||||
* #7229: rst domain: Non intended behavior is removed such as ``numref_`` links
|
||||
to ``.. rst:role:: numref``
|
||||
* #6903: py domain: Internal data structure has changed. Both objects and
|
||||
modules have node_id for cross reference
|
||||
* #6903: py domain: Non intended behavior is removed such as ``say_hello_``
|
||||
links to ``.. py:function:: say_hello()``
|
||||
* #7246: py domain: Drop special cross reference helper for exceptions,
|
||||
functions and methods
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
@ -71,6 +70,7 @@ Deprecated
|
||||
* ``desc_signature['first']``
|
||||
* ``sphinx.directives.DescDirective``
|
||||
* ``sphinx.domains.std.StandardDomain.add_object()``
|
||||
* ``sphinx.domains.python.PyDecoratorMixin``
|
||||
* ``sphinx.parsers.Parser.app``
|
||||
* ``sphinx.testing.path.Path.text()``
|
||||
* ``sphinx.testing.path.Path.bytes()``
|
||||
@ -87,6 +87,9 @@ Features added
|
||||
* #6830: autodoc: consider a member private if docstring contains
|
||||
``:meta private:`` in info-field-list
|
||||
* #7165: autodoc: Support Annotated type (PEP-593)
|
||||
* #2815: autodoc: Support singledispatch functions and methods
|
||||
* #7079: autodoc: :confval:`autodoc_typehints` accepts ``"description"``
|
||||
configuration. It shows typehints as object description
|
||||
* #6558: glossary: emit a warning for duplicated glossary entry
|
||||
* #3106: domain: Register hyperlink target for index page automatically
|
||||
* #6558: std domain: emit a warning for duplicated generic objects
|
||||
@ -120,8 +123,11 @@ Bugs fixed
|
||||
* C++, suppress warnings for directly dependent typenames in cross references
|
||||
generated automatically in signatures.
|
||||
* #5637: autodoc: Incorrect handling of nested class names on show-inheritance
|
||||
* #7267: autodoc: error message for invalid directive options has wrong location
|
||||
* #5637: inheritance_diagram: Incorrect handling of nested class names
|
||||
* #7139: ``code-block:: guess`` does not work
|
||||
* #7278: html search: Fix use of ``html_file_suffix`` instead of
|
||||
``html_link_suffix`` in search results
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
@ -5,7 +5,6 @@ PYTHON ?= python3
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = $(PYTHON) ../sphinx/cmd/build.py
|
||||
SPHINXPROJ = sphinx
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
|
@ -7,6 +7,7 @@ import sphinx
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
|
||||
'sphinx.ext.autosummary', 'sphinx.ext.extlinks',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode', 'sphinx.ext.inheritance_diagram']
|
||||
|
||||
master_doc = 'contents'
|
||||
|
@ -61,6 +61,11 @@ The following is a list of deprecated interfaces.
|
||||
- 5.0
|
||||
- ``sphinx.domains.std.StandardDomain.note_object()``
|
||||
|
||||
* - ``sphinx.domains.python.PyDecoratorMixin``
|
||||
- 3.0
|
||||
- 5.0
|
||||
- N/A
|
||||
|
||||
* - ``sphinx.parsers.Parser.app``
|
||||
- 3.0
|
||||
- 5.0
|
||||
|
@ -7,7 +7,6 @@ if "%SPHINXBUILD%" == "" (
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=sphinx-doc
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
|
@ -466,9 +466,13 @@ There are also config values that you can set:
|
||||
following values:
|
||||
|
||||
* ``'signature'`` -- Show typehints as its signature (default)
|
||||
* ``'description'`` -- Show typehints as content of function or method
|
||||
* ``'none'`` -- Do not show typehints
|
||||
|
||||
.. versionadded:: 2.1
|
||||
.. versionadded:: 3.0
|
||||
|
||||
New option ``'description'`` is added.
|
||||
|
||||
.. confval:: autodoc_warningiserror
|
||||
|
||||
@ -596,24 +600,3 @@ member should be included in the documentation by using the following event:
|
||||
``inherited_members``, ``undoc_members``, ``show_inheritance`` and
|
||||
``noindex`` that are true if the flag option of same name was given to the
|
||||
auto directive
|
||||
|
||||
Generating documents from type annotations
|
||||
------------------------------------------
|
||||
|
||||
As an experimental feature, autodoc provides ``sphinx.ext.autodoc.typehints`` as
|
||||
an additional extension. It extends autodoc itself to generate function document
|
||||
from its type annotations.
|
||||
|
||||
To enable the feature, please add ``sphinx.ext.autodoc.typehints`` to list of
|
||||
extensions and set `'description'` to :confval:`autodoc_typehints`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autodoc.typehints']
|
||||
|
||||
autodoc_typehints = 'description'
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Added as an experimental feature. This will be integrated into autodoc core
|
||||
in Sphinx-3.0.
|
||||
|
2
setup.py
2
setup.py
@ -48,7 +48,7 @@ extras_require = {
|
||||
'docutils-stubs',
|
||||
],
|
||||
'test': [
|
||||
'pytest < 5.3.3',
|
||||
'pytest',
|
||||
'pytest-cov',
|
||||
'html5lib',
|
||||
'typed_ast', # for py35-37
|
||||
|
@ -469,6 +469,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
'show_source': self.config.html_show_sourcelink,
|
||||
'sourcelink_suffix': self.config.html_sourcelink_suffix,
|
||||
'file_suffix': self.out_suffix,
|
||||
'link_suffix': self.link_suffix,
|
||||
'script_files': self.script_files,
|
||||
'language': self.config.language,
|
||||
'css_files': self.css_files,
|
||||
|
@ -25,7 +25,7 @@ from sphinx import addnodes
|
||||
from sphinx.addnodes import pending_xref, desc_signature
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.domains import Domain, ObjType, Index, IndexEntry
|
||||
from sphinx.environment import BuildEnvironment
|
||||
@ -439,8 +439,14 @@ class PyModulelevel(PyObject):
|
||||
"""
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
warnings.warn('PyModulelevel is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
for cls in self.__class__.__mro__:
|
||||
if cls.__name__ != 'DirectiveAdapter':
|
||||
warnings.warn('PyModulelevel is deprecated. '
|
||||
'Please check the implementation of %s' % cls,
|
||||
RemovedInSphinx40Warning)
|
||||
break
|
||||
else:
|
||||
warnings.warn('PyModulelevel is deprecated', RemovedInSphinx40Warning)
|
||||
|
||||
return super().run()
|
||||
|
||||
@ -485,6 +491,23 @@ class PyFunction(PyObject):
|
||||
return _('%s() (built-in function)') % name
|
||||
|
||||
|
||||
class PyDecoratorFunction(PyFunction):
|
||||
"""Description of a decorator."""
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
# a decorator function is a function after all
|
||||
self.name = 'py:function'
|
||||
return super().run()
|
||||
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
|
||||
ret = super().handle_signature(sig, signode)
|
||||
signode.insert(0, addnodes.desc_addname('@', '@'))
|
||||
return ret
|
||||
|
||||
def needs_arglist(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class PyVariable(PyObject):
|
||||
"""Description of a variable."""
|
||||
|
||||
@ -542,8 +565,14 @@ class PyClassmember(PyObject):
|
||||
"""
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
warnings.warn('PyClassmember is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
for cls in self.__class__.__mro__:
|
||||
if cls.__name__ != 'DirectiveAdapter':
|
||||
warnings.warn('PyClassmember is deprecated. '
|
||||
'Please check the implementation of %s' % cls,
|
||||
RemovedInSphinx40Warning)
|
||||
break
|
||||
else:
|
||||
warnings.warn('PyClassmember is deprecated', RemovedInSphinx40Warning)
|
||||
|
||||
return super().run()
|
||||
|
||||
@ -696,6 +725,22 @@ class PyStaticMethod(PyMethod):
|
||||
return super().run()
|
||||
|
||||
|
||||
class PyDecoratorMethod(PyMethod):
|
||||
"""Description of a decoratormethod."""
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
self.name = 'py:method'
|
||||
return super().run()
|
||||
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
|
||||
ret = super().handle_signature(sig, signode)
|
||||
signode.insert(0, addnodes.desc_addname('@', '@'))
|
||||
return ret
|
||||
|
||||
def needs_arglist(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class PyAttribute(PyObject):
|
||||
"""Description of an attribute."""
|
||||
|
||||
@ -738,6 +783,15 @@ class PyDecoratorMixin:
|
||||
Mixin for decorator directives.
|
||||
"""
|
||||
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
|
||||
for cls in self.__class__.__mro__:
|
||||
if cls.__name__ != 'DirectiveAdapter':
|
||||
warnings.warn('PyDecoratorMixin is deprecated. '
|
||||
'Please check the implementation of %s' % cls,
|
||||
RemovedInSphinx50Warning)
|
||||
break
|
||||
else:
|
||||
warnings.warn('PyDecoratorMixin is deprecated', RemovedInSphinx50Warning)
|
||||
|
||||
ret = super().handle_signature(sig, signode) # type: ignore
|
||||
signode.insert(0, addnodes.desc_addname('@', '@'))
|
||||
return ret
|
||||
@ -746,25 +800,6 @@ class PyDecoratorMixin:
|
||||
return False
|
||||
|
||||
|
||||
class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel):
|
||||
"""
|
||||
Directive to mark functions meant to be used as decorators.
|
||||
"""
|
||||
def run(self) -> List[Node]:
|
||||
# a decorator function is a function after all
|
||||
self.name = 'py:function'
|
||||
return super().run()
|
||||
|
||||
|
||||
class PyDecoratorMethod(PyDecoratorMixin, PyClassmember):
|
||||
"""
|
||||
Directive to mark methods meant to be used as decorators.
|
||||
"""
|
||||
def run(self) -> List[Node]:
|
||||
self.name = 'py:method'
|
||||
return super().run()
|
||||
|
||||
|
||||
class PyModule(SphinxDirective):
|
||||
"""
|
||||
Directive to mark description of a new module.
|
||||
@ -1106,14 +1141,6 @@ class PythonDomain(Domain):
|
||||
elif modname and classname and \
|
||||
modname + '.' + classname + '.' + name in self.objects:
|
||||
newname = modname + '.' + classname + '.' + name
|
||||
# special case: builtin exceptions have module "exceptions" set
|
||||
elif type == 'exc' and '.' not in name and \
|
||||
'exceptions.' + name in self.objects:
|
||||
newname = 'exceptions.' + name
|
||||
# special case: object methods
|
||||
elif type in ('func', 'meth') and '.' not in name and \
|
||||
'object.' + name in self.objects:
|
||||
newname = 'object.' + name
|
||||
if newname is not None:
|
||||
matches.append((newname, self.objects[newname]))
|
||||
return matches
|
||||
|
@ -16,6 +16,7 @@ import warnings
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
from docutils.statemachine import StringList
|
||||
|
||||
@ -1002,7 +1003,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
(inspect.isroutine(member) and isinstance(parent, ModuleDocumenter)))
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.env.config.autodoc_typehints == 'none':
|
||||
if self.env.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
|
||||
if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
|
||||
@ -1055,6 +1056,62 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
self.add_line(' :async:', sourcename)
|
||||
|
||||
|
||||
class SingledispatchFunctionDocumenter(FunctionDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for singledispatch'ed functions.
|
||||
"""
|
||||
objtype = 'singledispatch_function'
|
||||
directivetype = 'function'
|
||||
member_order = 30
|
||||
|
||||
# before FunctionDocumenter
|
||||
priority = FunctionDocumenter.priority + 1
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
return (super().can_document_member(member, membername, isattr, parent) and
|
||||
inspect.is_singledispatch_function(member))
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
sourcename = self.get_sourcename()
|
||||
|
||||
# intercept generated directive headers
|
||||
# TODO: It is very hacky to use mock to intercept header generation
|
||||
with patch.object(self, 'add_line') as add_line:
|
||||
super().add_directive_header(sig)
|
||||
|
||||
# output first line of header
|
||||
self.add_line(*add_line.call_args_list[0][0])
|
||||
|
||||
# inserts signature of singledispatch'ed functions
|
||||
for typ, func in self.object.registry.items():
|
||||
if typ is object:
|
||||
pass # default implementation. skipped.
|
||||
else:
|
||||
self.annotate_to_first_argument(func, typ)
|
||||
|
||||
documenter = FunctionDocumenter(self.directive, '')
|
||||
documenter.object = func
|
||||
self.add_line(' %s%s' % (self.format_name(),
|
||||
documenter.format_signature()),
|
||||
sourcename)
|
||||
|
||||
# output remains of directive header
|
||||
for call in add_line.call_args_list[1:]:
|
||||
self.add_line(*call[0])
|
||||
|
||||
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
|
||||
"""Annotate type hint to the first argument of function if needed."""
|
||||
sig = inspect.signature(func)
|
||||
if len(sig.parameters) == 0:
|
||||
return
|
||||
|
||||
name = list(sig.parameters)[0]
|
||||
if name not in func.__annotations__:
|
||||
func.__annotations__[name] = typ
|
||||
|
||||
|
||||
class DecoratorDocumenter(FunctionDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for decorator functions.
|
||||
@ -1399,6 +1456,66 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
pass
|
||||
|
||||
|
||||
class SingledispatchMethodDocumenter(MethodDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for singledispatch'ed methods.
|
||||
"""
|
||||
objtype = 'singledispatch_method'
|
||||
directivetype = 'method'
|
||||
member_order = 50
|
||||
|
||||
# before MethodDocumenter
|
||||
priority = MethodDocumenter.priority + 1
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
if super().can_document_member(member, membername, isattr, parent) and parent.object:
|
||||
meth = parent.object.__dict__.get(membername)
|
||||
return inspect.is_singledispatch_method(meth)
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
sourcename = self.get_sourcename()
|
||||
|
||||
# intercept generated directive headers
|
||||
# TODO: It is very hacky to use mock to intercept header generation
|
||||
with patch.object(self, 'add_line') as add_line:
|
||||
super().add_directive_header(sig)
|
||||
|
||||
# output first line of header
|
||||
self.add_line(*add_line.call_args_list[0][0])
|
||||
|
||||
# inserts signature of singledispatch'ed functions
|
||||
meth = self.parent.__dict__.get(self.objpath[-1])
|
||||
for typ, func in meth.dispatcher.registry.items():
|
||||
if typ is object:
|
||||
pass # default implementation. skipped.
|
||||
else:
|
||||
self.annotate_to_first_argument(func, typ)
|
||||
|
||||
documenter = MethodDocumenter(self.directive, '')
|
||||
documenter.object = func
|
||||
self.add_line(' %s%s' % (self.format_name(),
|
||||
documenter.format_signature()),
|
||||
sourcename)
|
||||
|
||||
# output remains of directive header
|
||||
for call in add_line.call_args_list[1:]:
|
||||
self.add_line(*call[0])
|
||||
|
||||
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
|
||||
"""Annotate type hint to the first argument of function if needed."""
|
||||
sig = inspect.signature(func, bound_method=True)
|
||||
if len(sig.parameters) == 0:
|
||||
return
|
||||
|
||||
name = list(sig.parameters)[0]
|
||||
if name not in func.__annotations__:
|
||||
func.__annotations__[name] = typ
|
||||
|
||||
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for attributes.
|
||||
@ -1611,8 +1728,10 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_autodocumenter(DataDocumenter)
|
||||
app.add_autodocumenter(DataDeclarationDocumenter)
|
||||
app.add_autodocumenter(FunctionDocumenter)
|
||||
app.add_autodocumenter(SingledispatchFunctionDocumenter)
|
||||
app.add_autodocumenter(DecoratorDocumenter)
|
||||
app.add_autodocumenter(MethodDocumenter)
|
||||
app.add_autodocumenter(SingledispatchMethodDocumenter)
|
||||
app.add_autodocumenter(AttributeDocumenter)
|
||||
app.add_autodocumenter(PropertyDocumenter)
|
||||
app.add_autodocumenter(InstanceAttributeDocumenter)
|
||||
@ -1624,7 +1743,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
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)
|
||||
app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "none"))
|
||||
app.add_config_value('autodoc_typehints', "signature", True,
|
||||
ENUM("signature", "description", "none"))
|
||||
app.add_config_value('autodoc_warningiserror', True, True)
|
||||
app.add_config_value('autodoc_inherit_docstrings', True, True)
|
||||
app.add_event('autodoc-before-process-signature')
|
||||
@ -1633,5 +1753,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_event('autodoc-skip-member')
|
||||
|
||||
app.setup_extension('sphinx.ext.autodoc.type_comment')
|
||||
app.setup_extension('sphinx.ext.autodoc.typehints')
|
||||
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
@ -133,7 +133,7 @@ class AutodocDirective(SphinxDirective):
|
||||
except (KeyError, ValueError, TypeError) as exc:
|
||||
# an option is either unknown or has a wrong type
|
||||
logger.error('An option to %s is either unknown or has an invalid value: %s' %
|
||||
(self.name, exc), location=(source, lineno))
|
||||
(self.name, exc), location=(self.env.docname, lineno))
|
||||
return []
|
||||
|
||||
# generate the output
|
||||
|
@ -18,21 +18,9 @@ from docutils.nodes import Element
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.config import Config, ENUM
|
||||
from sphinx.util import inspect, typing
|
||||
|
||||
|
||||
def config_inited(app: Sphinx, config: Config) -> None:
|
||||
if config.autodoc_typehints == 'description':
|
||||
# HACK: override this to make autodoc suppressing typehints in signatures
|
||||
config.autodoc_typehints = 'none' # type: ignore
|
||||
|
||||
# preserve user settings
|
||||
app._autodoc_typehints_description = True # type: ignore
|
||||
else:
|
||||
app._autodoc_typehints_description = False # type: ignore
|
||||
|
||||
|
||||
def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
options: Dict, args: str, retann: str) -> None:
|
||||
"""Record type hints to env object."""
|
||||
@ -53,7 +41,7 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None:
|
||||
if domain != 'py':
|
||||
return
|
||||
if app._autodoc_typehints_description is False: # type: ignore
|
||||
if app.config.autodoc_typehints != 'description':
|
||||
return
|
||||
|
||||
signature = cast(addnodes.desc_signature, contentnode.parent[0])
|
||||
@ -141,10 +129,6 @@ def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> No
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.setup_extension('sphinx.ext.autodoc')
|
||||
app.config.values['autodoc_typehints'] = ('signature', True,
|
||||
ENUM("signature", "description", "none"))
|
||||
app.connect('config-inited', config_inited)
|
||||
app.connect('autodoc-process-signature', record_typehints)
|
||||
app.connect('object-description-transform', merge_typehints)
|
||||
|
||||
|
@ -68,12 +68,14 @@ def setup_documenters(app: Any) -> None:
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||
SingledispatchFunctionDocumenter,
|
||||
)
|
||||
documenters = [
|
||||
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
|
||||
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
|
||||
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
|
||||
SlotsAttributeDocumenter, DataDeclarationDocumenter,
|
||||
SingledispatchFunctionDocumenter,
|
||||
] # type: List[Type[Documenter]]
|
||||
for documenter in documenters:
|
||||
app.registry.add_documenter(documenter.objtype, documenter)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ var DOCUMENTATION_OPTIONS = {
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: '{{ builder }}',
|
||||
FILE_SUFFIX: '{{ file_suffix }}',
|
||||
LINK_SUFFIX: '{{ link_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }},
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}',
|
||||
NAVIGATION_WITH_KEYS: {{ 'true' if theme_navigation_with_keys|tobool else 'false'}}
|
||||
|
@ -251,6 +251,7 @@ var Search = {
|
||||
var item = results.pop();
|
||||
var listItem = $('<li style="display:none"></li>');
|
||||
var requestUrl = "";
|
||||
var linkUrl = "";
|
||||
if (DOCUMENTATION_OPTIONS.BUILDER === 'dirhtml') {
|
||||
// dirhtml builder
|
||||
var dirname = item[0] + '/';
|
||||
@ -260,13 +261,15 @@ var Search = {
|
||||
dirname = '';
|
||||
}
|
||||
requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + dirname;
|
||||
linkUrl = requestUrl;
|
||||
|
||||
} else {
|
||||
// normal html builders
|
||||
requestUrl = DOCUMENTATION_OPTIONS.URL_ROOT + item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX;
|
||||
linkUrl = item[0] + DOCUMENTATION_OPTIONS.LINK_SUFFIX;
|
||||
}
|
||||
listItem.append($('<a/>').attr('href',
|
||||
requestUrl +
|
||||
linkUrl +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
if (item[3]) {
|
||||
listItem.append($('<span> (' + item[3] + ')</span>'));
|
||||
|
@ -224,6 +224,26 @@ def isattributedescriptor(obj: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def is_singledispatch_function(obj: Any) -> bool:
|
||||
"""Check if the object is singledispatch function."""
|
||||
if (inspect.isfunction(obj) and
|
||||
hasattr(obj, 'dispatch') and
|
||||
hasattr(obj, 'register') and
|
||||
obj.dispatch.__module__ == 'functools'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def is_singledispatch_method(obj: Any) -> bool:
|
||||
"""Check if the object is singledispatch method."""
|
||||
try:
|
||||
from functools import singledispatchmethod # type: ignore
|
||||
return isinstance(obj, singledispatchmethod)
|
||||
except ImportError: # py35-37
|
||||
return False
|
||||
|
||||
|
||||
def isfunction(obj: Any) -> bool:
|
||||
"""Check if the object is function."""
|
||||
return inspect.isfunction(unwrap(obj))
|
||||
|
@ -51,3 +51,11 @@ module
|
||||
.. py:attribute:: attr2
|
||||
|
||||
:type: :doc:`index`
|
||||
|
||||
.. py:module:: exceptions
|
||||
|
||||
.. py:exception:: Exception
|
||||
|
||||
.. py:module:: object
|
||||
|
||||
.. py:function:: sum()
|
||||
|
19
tests/roots/test-ext-autodoc/target/singledispatch.py
Normal file
19
tests/roots/test-ext-autodoc/target/singledispatch.py
Normal file
@ -0,0 +1,19 @@
|
||||
from functools import singledispatch
|
||||
|
||||
|
||||
@singledispatch
|
||||
def func(arg, kwarg=None):
|
||||
"""A function for general use."""
|
||||
pass
|
||||
|
||||
|
||||
@func.register(int)
|
||||
def _func_int(arg, kwarg=None):
|
||||
"""A function for int."""
|
||||
pass
|
||||
|
||||
|
||||
@func.register(str)
|
||||
def _func_str(arg, kwarg=None):
|
||||
"""A function for str."""
|
||||
pass
|
20
tests/roots/test-ext-autodoc/target/singledispatchmethod.py
Normal file
20
tests/roots/test-ext-autodoc/target/singledispatchmethod.py
Normal file
@ -0,0 +1,20 @@
|
||||
from functools import singledispatchmethod
|
||||
|
||||
|
||||
class Foo:
|
||||
"""docstring"""
|
||||
|
||||
@singledispatchmethod
|
||||
def meth(self, arg, kwarg=None):
|
||||
"""A method for general use."""
|
||||
pass
|
||||
|
||||
@meth.register(int)
|
||||
def _meth_int(self, arg, kwarg=None):
|
||||
"""A method for int."""
|
||||
pass
|
||||
|
||||
@meth.register(str)
|
||||
def _meth_str(self, arg, kwarg=None):
|
||||
"""A method for str."""
|
||||
pass
|
@ -1563,3 +1563,49 @@ def test_autodoc_for_egged_code(app):
|
||||
' :module: sample',
|
||||
''
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_singledispatch():
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.singledispatch', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.singledispatch',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: func(arg, kwarg=None)',
|
||||
' func(arg: int, kwarg=None)',
|
||||
' func(arg: str, kwarg=None)',
|
||||
' :module: target.singledispatch',
|
||||
'',
|
||||
' A function for general use.',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8),
|
||||
reason='singledispatchmethod is available since python3.8')
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_singledispatchmethod():
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.singledispatchmethod',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo',
|
||||
' :module: target.singledispatchmethod',
|
||||
'',
|
||||
' docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: Foo.meth(arg, kwarg=None)',
|
||||
' Foo.meth(arg: int, kwarg=None)',
|
||||
' Foo.meth(arg: str, kwarg=None)',
|
||||
' :module: target.singledispatchmethod',
|
||||
' ',
|
||||
' A method for general use.',
|
||||
' '
|
||||
]
|
||||
|
@ -585,6 +585,36 @@ def test_pyattribute(app):
|
||||
assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute')
|
||||
|
||||
|
||||
def test_pydecorator_signature(app):
|
||||
text = ".. py:decorator:: deco"
|
||||
domain = app.env.get_domain('py')
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_addname, "@"],
|
||||
[desc_name, "deco"])],
|
||||
desc_content)]))
|
||||
assert_node(doctree[1], addnodes.desc, desctype="function",
|
||||
domain="py", objtype="function", noindex=False)
|
||||
|
||||
assert 'deco' in domain.objects
|
||||
assert domain.objects['deco'] == ('index', 'deco', 'function')
|
||||
|
||||
|
||||
def test_pydecoratormethod_signature(app):
|
||||
text = ".. py:decoratormethod:: deco"
|
||||
domain = app.env.get_domain('py')
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
assert_node(doctree, (addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_addname, "@"],
|
||||
[desc_name, "deco"])],
|
||||
desc_content)]))
|
||||
assert_node(doctree[1], addnodes.desc, desctype="method",
|
||||
domain="py", objtype="method", noindex=False)
|
||||
|
||||
assert 'deco' in domain.objects
|
||||
assert domain.objects['deco'] == ('index', 'deco', 'method')
|
||||
|
||||
|
||||
@pytest.mark.sphinx(freshenv=True)
|
||||
def test_module_index(app):
|
||||
text = (".. py:module:: docutils\n"
|
||||
|
@ -462,10 +462,9 @@ def test_mocked_module_imports(app, warning):
|
||||
assert warning.getvalue() == ''
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "signature"})
|
||||
def test_autodoc_typehints_signature(app):
|
||||
app.config.autodoc_typehints = "signature"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
@ -513,10 +512,9 @@ def test_autodoc_typehints_signature(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "none"})
|
||||
def test_autodoc_typehints_none(app):
|
||||
app.config.autodoc_typehints = "none"
|
||||
|
||||
options = {"members": None,
|
||||
"undoc-members": True}
|
||||
actual = do_autodoc(app, 'module', 'target.typehints', options)
|
||||
@ -564,8 +562,7 @@ def test_autodoc_typehints_none(app):
|
||||
|
||||
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||
confoverrides={'extensions': ['sphinx.ext.autodoc.typehints'],
|
||||
'autodoc_typehints': 'description'})
|
||||
confoverrides={'autodoc_typehints': "description"})
|
||||
def test_autodoc_typehints_description(app):
|
||||
app.build()
|
||||
context = (app.outdir / 'index.txt').read_text()
|
||||
|
Loading…
Reference in New Issue
Block a user