summaryrefslogtreecommitdiff
path: root/sphinx/ext/autodoc/preserve_defaults.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/ext/autodoc/preserve_defaults.py')
-rw-r--r--sphinx/ext/autodoc/preserve_defaults.py88
1 files changed, 88 insertions, 0 deletions
diff --git a/sphinx/ext/autodoc/preserve_defaults.py b/sphinx/ext/autodoc/preserve_defaults.py
new file mode 100644
index 000000000..3d859fe8e
--- /dev/null
+++ b/sphinx/ext/autodoc/preserve_defaults.py
@@ -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
+ }