forked from sphinx-doc/sphinx
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix sphinx-doc#759: autodoc: Add sphinx.ext.autodoc.preserve_defaults…
… extension Add a new extension `sphinx.ext.autodoc.preserve_defaults`. It preserves the default argument values of function signatures in source code and keep them not evaluated for readability. This is an experimental extension and it will be integrated into autodoc core in Sphinx-4.0.
- Loading branch information
Showing
6 changed files
with
166 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', | ||
'', | ||
] |