mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-11-02 20:30:30 -06:00
Merge pull request #8007 from tk0miya/6518_autodoc_typehints_evaluation
Close #6518: autodoc: Add autodoc_type_aliases
This commit is contained in:
6 files changed
+144
-16
No files matched your search
@@ -515,6 +515,44 @@ There are also config values that you can set:
|
||||
|
||||
New option ``'description'`` is added.
|
||||
|
||||
.. confval:: autodoc_type_aliases
|
||||
|
||||
A dictionary for users defined `type aliases`__ that maps a type name to the
|
||||
full-qualified object name. It is used to keep type aliases not evaluated in
|
||||
the document. Defaults to empty (``{}``).
|
||||
|
||||
The type aliases are only available if your program enables `Postponed
|
||||
Evaluation of Annotations (PEP 563)`__ feature via ``from __future__ import
|
||||
annotations``.
|
||||
|
||||
For example, there is code using a type alias::
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
AliasType = Union[List[Dict[Tuple[int, str], Set[int]]], Tuple[str, List[str]]]
|
||||
|
||||
def f() -> AliasType:
|
||||
...
|
||||
|
||||
If ``autodoc_type_aliases`` is not set, autodoc will generate internal mark-up
|
||||
from this code as following::
|
||||
|
||||
.. py:function:: f() -> Union[List[Dict[Tuple[int, str], Set[int]]], Tuple[str, List[str]]]
|
||||
|
||||
...
|
||||
|
||||
If you set ``autodoc_type_aliases`` as
|
||||
``{'AliasType': 'your.module.TypeAlias'}``, it generates a following document
|
||||
internally::
|
||||
|
||||
.. py:function:: f() -> your.module.AliasType:
|
||||
|
||||
...
|
||||
|
||||
.. __: https://www.python.org/dev/peps/pep-0563/
|
||||
.. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases
|
||||
.. versionadded:: 3.3
|
||||
|
||||
.. confval:: autodoc_warningiserror
|
||||
|
||||
This value controls the behavior of :option:`sphinx-build -W` during
|
||||
|
||||
@@ -1213,7 +1213,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
|
||||
try:
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
sig = inspect.signature(self.object, follow_wrapped=True)
|
||||
sig = inspect.signature(self.object, follow_wrapped=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
args = stringify_signature(sig, **kwargs)
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a function signature for %s: %s"),
|
||||
@@ -1262,7 +1263,9 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
if overloaded:
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
overload = evaluate_signature(overload, __globals__,
|
||||
self.env.config.autodoc_type_aliases)
|
||||
|
||||
sig = stringify_signature(overload, **kwargs)
|
||||
sigs.append(sig)
|
||||
|
||||
@@ -1271,7 +1274,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
|
||||
"""Annotate type hint to the first argument of function if needed."""
|
||||
try:
|
||||
sig = inspect.signature(func)
|
||||
sig = inspect.signature(func, type_aliases=self.env.config.autodoc_type_aliases)
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a function signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
@@ -1392,7 +1395,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if call is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', call, True)
|
||||
try:
|
||||
sig = inspect.signature(call, bound_method=True)
|
||||
sig = inspect.signature(call, bound_method=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
return type(self.object), '__call__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -1407,7 +1411,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if new is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', new, True)
|
||||
try:
|
||||
sig = inspect.signature(new, bound_method=True)
|
||||
sig = inspect.signature(new, bound_method=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
return self.object, '__new__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -1417,7 +1422,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
if init is not None:
|
||||
self.env.app.emit('autodoc-before-process-signature', init, True)
|
||||
try:
|
||||
sig = inspect.signature(init, bound_method=True)
|
||||
sig = inspect.signature(init, bound_method=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
return self.object, '__init__', sig
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -1428,7 +1434,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
# the signature from, so just pass the object itself to our hook.
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
try:
|
||||
sig = inspect.signature(self.object, bound_method=False)
|
||||
sig = inspect.signature(self.object, bound_method=False,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
return None, None, sig
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -1475,7 +1482,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
method = safe_getattr(self._signature_class, self._signature_method_name, None)
|
||||
__globals__ = safe_getattr(method, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get(qualname):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
overload = evaluate_signature(overload, __globals__,
|
||||
self.env.config.autodoc_type_aliases)
|
||||
|
||||
parameters = list(overload.parameters.values())
|
||||
overload = overload.replace(parameters=parameters[1:],
|
||||
@@ -1820,11 +1828,13 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
else:
|
||||
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, False)
|
||||
sig = inspect.signature(self.object, bound_method=False)
|
||||
sig = inspect.signature(self.object, bound_method=False,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
else:
|
||||
self.env.app.emit('autodoc-before-process-signature', self.object, True)
|
||||
sig = inspect.signature(self.object, bound_method=True,
|
||||
follow_wrapped=True)
|
||||
follow_wrapped=True,
|
||||
type_aliases=self.env.config.autodoc_type_aliases)
|
||||
args = stringify_signature(sig, **kwargs)
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a method signature for %s: %s"),
|
||||
@@ -1884,7 +1894,9 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
if overloaded:
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
overload = evaluate_signature(overload, __globals__)
|
||||
overload = evaluate_signature(overload, __globals__,
|
||||
self.env.config.autodoc_type_aliases)
|
||||
|
||||
if not inspect.isstaticmethod(self.object, cls=self.parent,
|
||||
name=self.object_name):
|
||||
parameters = list(overload.parameters.values())
|
||||
@@ -1897,7 +1909,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
|
||||
"""Annotate type hint to the first argument of function if needed."""
|
||||
try:
|
||||
sig = inspect.signature(func)
|
||||
sig = inspect.signature(func, type_aliases=self.env.config.autodoc_type_aliases)
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a method signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
@@ -2237,6 +2249,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_config_value('autodoc_mock_imports', [], True)
|
||||
app.add_config_value('autodoc_typehints', "signature", True,
|
||||
ENUM("signature", "description", "none"))
|
||||
app.add_config_value('autodoc_type_aliases', {}, True)
|
||||
app.add_config_value('autodoc_warningiserror', True, True)
|
||||
app.add_config_value('autodoc_inherit_docstrings', True, True)
|
||||
app.add_event('autodoc-before-process-signature')
|
||||
|
||||
@@ -439,8 +439,8 @@ def _should_unwrap(subject: Callable) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False
|
||||
) -> inspect.Signature:
|
||||
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False,
|
||||
type_aliases: Dict = {}) -> inspect.Signature:
|
||||
"""Return a Signature object for the given *subject*.
|
||||
|
||||
:param bound_method: Specify *subject* is a bound method or not
|
||||
@@ -470,7 +470,7 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo
|
||||
|
||||
try:
|
||||
# Update unresolved annotations using ``get_type_hints()``.
|
||||
annotations = typing.get_type_hints(subject)
|
||||
annotations = typing.get_type_hints(subject, None, type_aliases)
|
||||
for i, param in enumerate(parameters):
|
||||
if isinstance(param.annotation, str) and param.name in annotations:
|
||||
parameters[i] = param.replace(annotation=annotations[param.name])
|
||||
|
||||
@@ -63,7 +63,11 @@ def is_system_TypeVar(typ: Any) -> bool:
|
||||
def stringify(annotation: Any) -> str:
|
||||
"""Stringify type annotation object."""
|
||||
if isinstance(annotation, str):
|
||||
return annotation
|
||||
if annotation.startswith("'") and annotation.endswith("'"):
|
||||
# might be a double Forward-ref'ed type. Go unquoting.
|
||||
return annotation[1:-2]
|
||||
else:
|
||||
return annotation
|
||||
elif isinstance(annotation, TypeVar): # type: ignore
|
||||
return annotation.__name__
|
||||
elif not annotation:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
from typing import overload
|
||||
|
||||
|
||||
myint = int
|
||||
|
||||
|
||||
def sum(x: myint, y: myint) -> myint:
|
||||
"""docstring"""
|
||||
return x + y
|
||||
|
||||
|
||||
@overload
|
||||
def mult(x: myint, y: myint) -> myint:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def mult(x: float, y: float) -> float:
|
||||
...
|
||||
|
||||
|
||||
def mult(x, y):
|
||||
"""docstring"""
|
||||
return x, y
|
||||
@@ -642,6 +642,54 @@ def test_autodoc_typehints_description_for_invalid_node(app):
|
||||
restructuredtext.parse(app, text) # raises no error
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc')
|
||||
def test_autodoc_type_aliases(app):
|
||||
# default
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.annotations', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.annotations',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: mult(x: int, y: int) -> int',
|
||||
' mult(x: float, y: float) -> float',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: sum(x: int, y: int) -> int',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
# define aliases
|
||||
app.config.autodoc_type_aliases = {'myint': 'myint'}
|
||||
actual = do_autodoc(app, 'module', 'target.annotations', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.annotations',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: mult(x: myint, y: myint) -> myint',
|
||||
' mult(x: float, y: float) -> float',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: sum(x: myint, y: myint) -> myint',
|
||||
' :module: target.annotations',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autodoc_default_options(app):
|
||||
# no settings
|
||||
|
||||
Reference in new issue
Block a user