mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #8771 from tk0miya/759_preserve_defaults
Fix #759: autodoc: Add sphinx.ext.autodoc.preserve_defaults extension
This commit is contained in:
3
CHANGES
3
CHANGES
@@ -192,6 +192,9 @@ Features added
|
||||
* #8775: autodoc: Support type union operator (PEP-604) in Python 3.10 or above
|
||||
* #8297: autodoc: Allow to extend :confval:`autodoc_default_options` via
|
||||
directive options
|
||||
* #759: autodoc: Add a new configuration :confval:`autodoc_preserve_defaults` as
|
||||
an experimental feature. It preserves the default argument values of
|
||||
functions in source code and keep them not evaluated for readability.
|
||||
* #8619: html: kbd role generates customizable HTML tags for compound keys
|
||||
* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter
|
||||
for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()`
|
||||
|
||||
@@ -586,6 +586,16 @@ There are also config values that you can set:
|
||||
.. __: https://mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases
|
||||
.. versionadded:: 3.3
|
||||
|
||||
.. confval:: autodoc_preserve_defaults
|
||||
|
||||
If True, the default argument values of functions will be not evaluated on
|
||||
generating document. It preserves them as is in the source code.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
Added as an experimental feature. This will be integrated into autodoc core
|
||||
in the future.
|
||||
|
||||
.. confval:: autodoc_warningiserror
|
||||
|
||||
This value controls the behavior of :option:`sphinx-build -W` during
|
||||
|
||||
@@ -2634,6 +2634,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
|
||||
app.connect('config-inited', migrate_autodoc_member_order, priority=800)
|
||||
|
||||
app.setup_extension('sphinx.ext.autodoc.preserve_defaults')
|
||||
app.setup_extension('sphinx.ext.autodoc.type_comment')
|
||||
app.setup_extension('sphinx.ext.autodoc.typehints')
|
||||
|
||||
|
||||
88
sphinx/ext/autodoc/preserve_defaults.py
Normal file
88
sphinx/ext/autodoc/preserve_defaults.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
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 ast
|
||||
import inspect
|
||||
from typing import Any, Dict
|
||||
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.locale import __
|
||||
from sphinx.pycode.ast import parse as ast_parse
|
||||
from sphinx.pycode.ast import unparse as ast_unparse
|
||||
from sphinx.util import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DefaultValue:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
def 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 None
|
||||
|
||||
|
||||
def 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
|
||||
}
|
||||
19
tests/roots/test-ext-autodoc/target/preserve_defaults.py
Normal file
19
tests/roots/test-ext-autodoc/target/preserve_defaults.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
CONSTANT = 'foo'
|
||||
SENTINEL = object()
|
||||
|
||||
|
||||
def foo(name: str = CONSTANT,
|
||||
sentinal: Any = SENTINEL,
|
||||
now: datetime = datetime.now()) -> None:
|
||||
"""docstring"""
|
||||
|
||||
|
||||
class Class:
|
||||
"""docstring"""
|
||||
|
||||
def meth(self, name: str = CONSTANT, sentinal: Any = SENTINEL,
|
||||
now: datetime = datetime.now()) -> None:
|
||||
"""docstring"""
|
||||
45
tests/test_ext_autodoc_preserve_defaults.py
Normal file
45
tests/test_ext_autodoc_preserve_defaults.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
test_ext_autodoc_preserve_defaults
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test the autodoc extension.
|
||||
|
||||
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from .test_ext_autodoc import do_autodoc
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_preserve_defaults': True})
|
||||
def test_preserve_defaults(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.preserve_defaults', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.preserve_defaults',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.preserve_defaults',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:method:: Class.meth(name: str = CONSTANT, sentinal: Any = SENTINEL, '
|
||||
'now: datetime.datetime = datetime.now()) -> None',
|
||||
' :module: target.preserve_defaults',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:function:: foo(name: str = CONSTANT, sentinal: Any = SENTINEL, now: '
|
||||
'datetime.datetime = datetime.now()) -> None',
|
||||
' :module: target.preserve_defaults',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
Reference in New Issue
Block a user