mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Enable automatic formatting for `sphinx/ext/autodoc/
`
This commit is contained in:
parent
f4b397f20e
commit
e81c4e865e
@ -415,13 +415,6 @@ exclude = [
|
||||
"sphinx/domains/python/_object.py",
|
||||
"sphinx/domains/rst.py",
|
||||
"sphinx/domains/std/__init__.py",
|
||||
"sphinx/ext/autodoc/__init__.py",
|
||||
"sphinx/ext/autodoc/directive.py",
|
||||
"sphinx/ext/autodoc/importer.py",
|
||||
"sphinx/ext/autodoc/mock.py",
|
||||
"sphinx/ext/autodoc/preserve_defaults.py",
|
||||
"sphinx/ext/autodoc/type_comment.py",
|
||||
"sphinx/ext/autodoc/typehints.py",
|
||||
"sphinx/ext/autosectionlabel.py",
|
||||
"sphinx/ext/autosummary/__init__.py",
|
||||
"sphinx/ext/coverage.py",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -23,13 +23,27 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# common option names for autodoc directives
|
||||
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
|
||||
'show-inheritance', 'private-members', 'special-members',
|
||||
'ignore-module-all', 'exclude-members', 'member-order',
|
||||
'imported-members', 'class-doc-from', 'no-value']
|
||||
AUTODOC_DEFAULT_OPTIONS = [
|
||||
'members',
|
||||
'undoc-members',
|
||||
'inherited-members',
|
||||
'show-inheritance',
|
||||
'private-members',
|
||||
'special-members',
|
||||
'ignore-module-all',
|
||||
'exclude-members',
|
||||
'member-order',
|
||||
'imported-members',
|
||||
'class-doc-from',
|
||||
'no-value',
|
||||
]
|
||||
|
||||
AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members',
|
||||
'exclude-members']
|
||||
AUTODOC_EXTENDABLE_OPTIONS = [
|
||||
'members',
|
||||
'private-members',
|
||||
'special-members',
|
||||
'exclude-members',
|
||||
]
|
||||
|
||||
|
||||
class DummyOptionSpec(dict[str, Callable[[str], str]]):
|
||||
@ -46,8 +60,14 @@ class DummyOptionSpec(dict[str, Callable[[str], str]]):
|
||||
class DocumenterBridge:
|
||||
"""A parameters container for Documenters."""
|
||||
|
||||
def __init__(self, env: BuildEnvironment, reporter: Reporter | None, options: Options,
|
||||
lineno: int, state: Any) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
env: BuildEnvironment,
|
||||
reporter: Reporter | None,
|
||||
options: Options,
|
||||
lineno: int,
|
||||
state: Any,
|
||||
) -> None:
|
||||
self.env = env
|
||||
self._reporter = reporter
|
||||
self.genopt = options
|
||||
@ -58,7 +78,7 @@ class DocumenterBridge:
|
||||
|
||||
|
||||
def process_documenter_options(
|
||||
documenter: type[Documenter], config: Config, options: dict[str, str],
|
||||
documenter: type[Documenter], config: Config, options: dict[str, str]
|
||||
) -> Options:
|
||||
"""Recognize options of Documenter from user input."""
|
||||
default_options = config.autodoc_default_options
|
||||
@ -83,8 +103,9 @@ def process_documenter_options(
|
||||
return Options(assemble_option_dict(options.items(), documenter.option_spec))
|
||||
|
||||
|
||||
def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter,
|
||||
) -> list[Node]:
|
||||
def parse_generated_content(
|
||||
state: RSTState, content: StringList, documenter: Documenter
|
||||
) -> list[Node]:
|
||||
"""Parse an item of content generated by Documenter."""
|
||||
with switch_source_input(state, content):
|
||||
if documenter.titles_allowed:
|
||||
@ -115,7 +136,8 @@ class AutodocDirective(SphinxDirective):
|
||||
|
||||
try:
|
||||
source, lineno = reporter.get_source_and_line( # type: ignore[attr-defined]
|
||||
self.lineno)
|
||||
self.lineno
|
||||
)
|
||||
except AttributeError:
|
||||
source, lineno = (None, None)
|
||||
logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
|
||||
@ -126,15 +148,23 @@ class AutodocDirective(SphinxDirective):
|
||||
|
||||
# process the options with the selected documenter's option_spec
|
||||
try:
|
||||
documenter_options = process_documenter_options(doccls, self.config, self.options)
|
||||
documenter_options = process_documenter_options(
|
||||
doccls, self.config, self.options
|
||||
)
|
||||
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=(self.env.docname, lineno))
|
||||
logger.error(
|
||||
'An option to %s is either unknown or has an invalid value: %s',
|
||||
self.name,
|
||||
exc,
|
||||
location=(self.env.docname, lineno),
|
||||
)
|
||||
return []
|
||||
|
||||
# generate the output
|
||||
params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state)
|
||||
params = DocumenterBridge(
|
||||
self.env, reporter, documenter_options, lineno, self.state
|
||||
)
|
||||
documenter = doccls(params, self.arguments[0])
|
||||
documenter.generate(more_content=self.content)
|
||||
if not params.result:
|
||||
|
@ -51,7 +51,13 @@ def _filter_enum_dict(
|
||||
candidate_in_mro: set[str] = set()
|
||||
# sunder names that were picked up (and thereby allowed to be redefined)
|
||||
# see: https://docs.python.org/3/howto/enum.html#supported-dunder-names
|
||||
sunder_names = {'_name_', '_value_', '_missing_', '_order_', '_generate_next_value_'}
|
||||
sunder_names = {
|
||||
'_name_',
|
||||
'_value_',
|
||||
'_missing_',
|
||||
'_order_',
|
||||
'_generate_next_value_',
|
||||
}
|
||||
# attributes that can be picked up on a mixin type or the enum's data type
|
||||
public_names = {'name', 'value', *object.__dict__, *sunder_names}
|
||||
# names that are ignored by default
|
||||
@ -88,8 +94,14 @@ def _filter_enum_dict(
|
||||
# exclude members coming from the native Enum unless
|
||||
# they were redefined on a mixin type or the data type
|
||||
excluded_members = Enum.__dict__.keys() - candidate_in_mro
|
||||
yield from filter(None, (query(name, enum_class) for name in enum_class_dict
|
||||
if name not in excluded_members))
|
||||
yield from filter(
|
||||
None,
|
||||
(
|
||||
query(name, enum_class)
|
||||
for name in enum_class_dict
|
||||
if name not in excluded_members
|
||||
),
|
||||
)
|
||||
|
||||
# check if allowed members from ``Enum`` were redefined at the enum level
|
||||
special_names = sunder_names | public_names
|
||||
@ -112,7 +124,7 @@ def mangle(subject: Any, name: str) -> str:
|
||||
"""Mangle the given name."""
|
||||
try:
|
||||
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
|
||||
return f"_{subject.__name__}{name}"
|
||||
return f'_{subject.__name__}{name}'
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@ -123,12 +135,12 @@ def unmangle(subject: Any, name: str) -> str | None:
|
||||
"""Unmangle the given name."""
|
||||
try:
|
||||
if isclass(subject) and not name.endswith('__'):
|
||||
prefix = "_%s__" % subject.__name__
|
||||
prefix = f'_{subject.__name__}__'
|
||||
if name.startswith(prefix):
|
||||
return name.replace(prefix, "__", 1)
|
||||
return name.replace(prefix, '__', 1)
|
||||
else:
|
||||
for cls in subject.__mro__:
|
||||
prefix = "_%s__" % cls.__name__
|
||||
prefix = f'_{cls.__name__}__'
|
||||
if name.startswith(prefix):
|
||||
# mangled attribute defined in parent class
|
||||
return None
|
||||
@ -160,8 +172,12 @@ def _reload_module(module: ModuleType) -> Any:
|
||||
raise ImportError(exc, traceback.format_exc()) from exc
|
||||
|
||||
|
||||
def import_object(modname: str, objpath: list[str], objtype: str = '',
|
||||
attrgetter: Callable[[Any, str], Any] = safe_getattr) -> Any:
|
||||
def import_object(
|
||||
modname: str,
|
||||
objpath: list[str],
|
||||
objtype: str = '',
|
||||
attrgetter: Callable[[Any, str], Any] = safe_getattr,
|
||||
) -> Any:
|
||||
if objpath:
|
||||
logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
|
||||
else:
|
||||
@ -176,7 +192,9 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
|
||||
original_module_names = frozenset(sys.modules)
|
||||
module = import_module(modname)
|
||||
if os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
|
||||
new_modules = [m for m in sys.modules if m not in original_module_names]
|
||||
new_modules = [
|
||||
m for m in sys.modules if m not in original_module_names
|
||||
]
|
||||
# Try reloading modules with ``typing.TYPE_CHECKING == True``.
|
||||
try:
|
||||
typing.TYPE_CHECKING = True
|
||||
@ -222,8 +240,11 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
|
||||
exc = exc_on_importing
|
||||
|
||||
if objpath:
|
||||
errmsg = ('autodoc: failed to import %s %r from module %r' %
|
||||
(objtype, '.'.join(objpath), modname))
|
||||
errmsg = 'autodoc: failed to import %s %r from module %r' % (
|
||||
objtype,
|
||||
'.'.join(objpath),
|
||||
modname,
|
||||
)
|
||||
else:
|
||||
errmsg = f'autodoc: failed to import {objtype} {modname!r}'
|
||||
|
||||
@ -232,14 +253,18 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
|
||||
# traceback
|
||||
real_exc, traceback_msg = exc.args
|
||||
if isinstance(real_exc, SystemExit):
|
||||
errmsg += ('; the module executes module level statement '
|
||||
'and it might call sys.exit().')
|
||||
errmsg += (
|
||||
'; the module executes module level statement '
|
||||
'and it might call sys.exit().'
|
||||
)
|
||||
elif isinstance(real_exc, ImportError) and real_exc.args:
|
||||
errmsg += '; the following exception was raised:\n%s' % real_exc.args[0]
|
||||
else:
|
||||
errmsg += '; the following exception was raised:\n%s' % traceback_msg
|
||||
else:
|
||||
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
|
||||
errmsg += (
|
||||
'; the following exception was raised:\n%s' % traceback.format_exc()
|
||||
)
|
||||
|
||||
logger.debug(errmsg)
|
||||
raise ImportError(errmsg) from exc
|
||||
@ -267,11 +292,17 @@ def get_object_members(
|
||||
|
||||
# enum members
|
||||
if isenumclass(subject):
|
||||
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
|
||||
for name, defining_class, value in _filter_enum_dict(
|
||||
subject, attrgetter, obj_dict
|
||||
):
|
||||
# the order of occurrence of *name* matches the subject's MRO,
|
||||
# allowing inherited attributes to be shadowed correctly
|
||||
if unmangled := unmangle(defining_class, name):
|
||||
members[unmangled] = Attribute(unmangled, defining_class is subject, value)
|
||||
members[unmangled] = Attribute(
|
||||
name=unmangled,
|
||||
directly_defined=defining_class is subject,
|
||||
value=value,
|
||||
)
|
||||
|
||||
# members in __slots__
|
||||
try:
|
||||
@ -280,7 +311,9 @@ def get_object_members(
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
for name in subject___slots__:
|
||||
members[name] = Attribute(name, True, SLOTSATTR)
|
||||
members[name] = Attribute(
|
||||
name=name, directly_defined=True, value=SLOTSATTR
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
@ -291,7 +324,9 @@ def get_object_members(
|
||||
directly_defined = name in obj_dict
|
||||
unmangled = unmangle(subject, name)
|
||||
if unmangled and unmangled not in members:
|
||||
members[unmangled] = Attribute(unmangled, directly_defined, value)
|
||||
members[unmangled] = Attribute(
|
||||
name=unmangled, directly_defined=directly_defined, value=value
|
||||
)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
@ -300,20 +335,25 @@ def get_object_members(
|
||||
for name in getannotations(cls):
|
||||
unmangled = unmangle(cls, name)
|
||||
if unmangled and unmangled not in members:
|
||||
members[unmangled] = Attribute(unmangled, cls is subject, INSTANCEATTR)
|
||||
members[unmangled] = Attribute(
|
||||
name=unmangled, directly_defined=cls is subject, value=INSTANCEATTR
|
||||
)
|
||||
|
||||
if analyzer:
|
||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||
namespace = '.'.join(objpath)
|
||||
for (ns, name) in analyzer.find_attr_docs():
|
||||
for ns, name in analyzer.find_attr_docs():
|
||||
if namespace == ns and name not in members:
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
members[name] = Attribute(
|
||||
name=name, directly_defined=True, value=INSTANCEATTR
|
||||
)
|
||||
|
||||
return members
|
||||
|
||||
|
||||
def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
|
||||
inherit_docstrings: bool = True) -> dict[str, ObjectMember]:
|
||||
def get_class_members(
|
||||
subject: Any, objpath: Any, attrgetter: Callable, inherit_docstrings: bool = True
|
||||
) -> dict[str, ObjectMember]:
|
||||
"""Get members and attributes of target class."""
|
||||
from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember
|
||||
|
||||
@ -324,11 +364,15 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
|
||||
|
||||
# enum members
|
||||
if isenumclass(subject):
|
||||
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
|
||||
for name, defining_class, value in _filter_enum_dict(
|
||||
subject, attrgetter, obj_dict
|
||||
):
|
||||
# the order of occurrence of *name* matches the subject's MRO,
|
||||
# allowing inherited attributes to be shadowed correctly
|
||||
if unmangled := unmangle(defining_class, name):
|
||||
members[unmangled] = ObjectMember(unmangled, value, class_=defining_class)
|
||||
members[unmangled] = ObjectMember(
|
||||
unmangled, value, class_=defining_class
|
||||
)
|
||||
|
||||
# members in __slots__
|
||||
try:
|
||||
@ -337,8 +381,9 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
for name, docstring in subject___slots__.items():
|
||||
members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
|
||||
docstring=docstring)
|
||||
members[name] = ObjectMember(
|
||||
name, SLOTSATTR, class_=subject, docstring=docstring
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
@ -380,19 +425,27 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
|
||||
else:
|
||||
docstring = None
|
||||
|
||||
members[unmangled] = ObjectMember(unmangled, INSTANCEATTR, class_=cls,
|
||||
docstring=docstring)
|
||||
members[unmangled] = ObjectMember(
|
||||
unmangled, INSTANCEATTR, class_=cls, docstring=docstring
|
||||
)
|
||||
|
||||
# append or complete instance attributes (cf. self.attr1) if analyzer knows
|
||||
if analyzer:
|
||||
for (ns, name), docstring in analyzer.attr_docs.items():
|
||||
if ns == qualname and name not in members:
|
||||
# otherwise unknown instance attribute
|
||||
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
|
||||
docstring='\n'.join(docstring))
|
||||
elif (ns == qualname and docstring and
|
||||
isinstance(members[name], ObjectMember) and
|
||||
not members[name].docstring):
|
||||
members[name] = ObjectMember(
|
||||
name,
|
||||
INSTANCEATTR,
|
||||
class_=cls,
|
||||
docstring='\n'.join(docstring),
|
||||
)
|
||||
elif (
|
||||
ns == qualname
|
||||
and docstring
|
||||
and isinstance(members[name], ObjectMember)
|
||||
and not members[name].docstring
|
||||
):
|
||||
if cls != subject and not inherit_docstrings:
|
||||
# If we are in the MRO of the class and not the class itself,
|
||||
# and we do not want to inherit docstrings, then skip setting
|
||||
|
@ -35,8 +35,12 @@ class _MockObject:
|
||||
superclass = args[1][-1].__class__
|
||||
if superclass is cls:
|
||||
# subclassing MockObject
|
||||
return _make_subclass(args[0], superclass.__display_name__,
|
||||
superclass=superclass, attributes=args[2])
|
||||
return _make_subclass(
|
||||
args[0],
|
||||
superclass.__display_name__,
|
||||
superclass=superclass,
|
||||
attributes=args[2],
|
||||
)
|
||||
|
||||
return super().__new__(cls)
|
||||
|
||||
@ -70,12 +74,19 @@ class _MockObject:
|
||||
return self.__display_name__
|
||||
|
||||
|
||||
def _make_subclass(name: str, module: str, superclass: Any = _MockObject,
|
||||
attributes: Any = None, decorator_args: tuple[Any, ...] = ()) -> Any:
|
||||
attrs = {'__module__': module,
|
||||
'__display_name__': module + '.' + name,
|
||||
'__name__': name,
|
||||
'__sphinx_decorator_args__': decorator_args}
|
||||
def _make_subclass(
|
||||
name: str,
|
||||
module: str,
|
||||
superclass: Any = _MockObject,
|
||||
attributes: Any = None,
|
||||
decorator_args: tuple[Any, ...] = (),
|
||||
) -> Any:
|
||||
attrs = {
|
||||
'__module__': module,
|
||||
'__display_name__': module + '.' + name,
|
||||
'__name__': name,
|
||||
'__sphinx_decorator_args__': decorator_args,
|
||||
}
|
||||
attrs.update(attributes or {})
|
||||
|
||||
return type(name, (superclass,), attrs)
|
||||
@ -124,8 +135,12 @@ class MockFinder(MetaPathFinder):
|
||||
self.loader = MockLoader(self)
|
||||
self.mocked_modules: list[str] = []
|
||||
|
||||
def find_spec(self, fullname: str, path: Sequence[bytes | str] | None,
|
||||
target: ModuleType | None = None) -> ModuleSpec | None:
|
||||
def find_spec(
|
||||
self,
|
||||
fullname: str,
|
||||
path: Sequence[bytes | str] | None,
|
||||
target: ModuleType | None = None,
|
||||
) -> ModuleSpec | None:
|
||||
for modname in self.modnames:
|
||||
# check if fullname is (or is a descendant of) one of our targets
|
||||
if modname == fullname or fullname.startswith(modname + '.'):
|
||||
|
@ -42,11 +42,15 @@ def get_function_def(obj: Any) -> ast.FunctionDef | None:
|
||||
This tries to parse original code for living object and returns
|
||||
AST node for given *obj*.
|
||||
"""
|
||||
warnings.warn('sphinx.ext.autodoc.preserve_defaults.get_function_def is'
|
||||
' deprecated and scheduled for removal in Sphinx 9.'
|
||||
' Use sphinx.ext.autodoc.preserve_defaults._get_arguments() to'
|
||||
' extract AST arguments objects from a lambda or regular'
|
||||
' function.', RemovedInSphinx90Warning, stacklevel=2)
|
||||
warnings.warn(
|
||||
'sphinx.ext.autodoc.preserve_defaults.get_function_def is'
|
||||
' deprecated and scheduled for removal in Sphinx 9.'
|
||||
' Use sphinx.ext.autodoc.preserve_defaults._get_arguments() to'
|
||||
' extract AST arguments objects from a lambda or regular'
|
||||
' function.',
|
||||
RemovedInSphinx90Warning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
try:
|
||||
source = inspect.getsource(obj)
|
||||
@ -113,7 +117,7 @@ def get_default_value(lines: list[str], position: ast.expr) -> str | None:
|
||||
try:
|
||||
if position.lineno == position.end_lineno:
|
||||
line = lines[position.lineno - 1]
|
||||
return line[position.col_offset:position.end_col_offset]
|
||||
return line[position.col_offset : position.end_col_offset]
|
||||
else:
|
||||
# multiline value is not supported now
|
||||
return None
|
||||
@ -187,7 +191,9 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
|
||||
# In this case, we can't set __signature__.
|
||||
return
|
||||
except NotImplementedError as exc: # failed to ast_unparse()
|
||||
logger.warning(__("Failed to parse a default argument value for %r: %s"), obj, exc)
|
||||
logger.warning(
|
||||
__('Failed to parse a default argument value for %r: %s'), obj, exc
|
||||
)
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
|
@ -32,35 +32,52 @@ def not_suppressed(argtypes: Sequence[ast.expr] = ()) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
|
||||
type_comment: ast.FunctionDef) -> Signature:
|
||||
def signature_from_ast(
|
||||
node: ast.FunctionDef, bound_method: bool, type_comment: ast.FunctionDef
|
||||
) -> Signature:
|
||||
"""Return a Signature object for the given *node*.
|
||||
|
||||
:param bound_method: Specify *node* is a bound method or not
|
||||
"""
|
||||
params = []
|
||||
for arg in node.args.posonlyargs:
|
||||
param = Parameter(arg.arg, Parameter.POSITIONAL_ONLY, annotation=arg.type_comment)
|
||||
param = Parameter(
|
||||
arg.arg,
|
||||
Parameter.POSITIONAL_ONLY,
|
||||
annotation=arg.type_comment,
|
||||
)
|
||||
params.append(param)
|
||||
|
||||
for arg in node.args.args:
|
||||
param = Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
|
||||
annotation=arg.type_comment or Parameter.empty)
|
||||
param = Parameter(
|
||||
arg.arg,
|
||||
Parameter.POSITIONAL_OR_KEYWORD,
|
||||
annotation=arg.type_comment or Parameter.empty,
|
||||
)
|
||||
params.append(param)
|
||||
|
||||
if node.args.vararg:
|
||||
param = Parameter(node.args.vararg.arg, Parameter.VAR_POSITIONAL,
|
||||
annotation=node.args.vararg.type_comment or Parameter.empty)
|
||||
param = Parameter(
|
||||
node.args.vararg.arg,
|
||||
Parameter.VAR_POSITIONAL,
|
||||
annotation=node.args.vararg.type_comment or Parameter.empty,
|
||||
)
|
||||
params.append(param)
|
||||
|
||||
for arg in node.args.kwonlyargs:
|
||||
param = Parameter(arg.arg, Parameter.KEYWORD_ONLY,
|
||||
annotation=arg.type_comment or Parameter.empty)
|
||||
param = Parameter(
|
||||
arg.arg,
|
||||
Parameter.KEYWORD_ONLY,
|
||||
annotation=arg.type_comment or Parameter.empty,
|
||||
)
|
||||
params.append(param)
|
||||
|
||||
if node.args.kwarg:
|
||||
param = Parameter(node.args.kwarg.arg, Parameter.VAR_KEYWORD,
|
||||
annotation=node.args.kwarg.type_comment or Parameter.empty)
|
||||
param = Parameter(
|
||||
node.args.kwarg.arg,
|
||||
Parameter.VAR_KEYWORD,
|
||||
annotation=node.args.kwarg.type_comment or Parameter.empty,
|
||||
)
|
||||
params.append(param)
|
||||
|
||||
# Remove first parameter when *obj* is bound_method
|
||||
@ -70,8 +87,7 @@ def signature_from_ast(node: ast.FunctionDef, bound_method: bool,
|
||||
# merge type_comment into signature
|
||||
if not_suppressed(type_comment.argtypes): # type: ignore[attr-defined]
|
||||
for i, param in enumerate(params):
|
||||
params[i] = param.replace(
|
||||
annotation=type_comment.argtypes[i]) # type: ignore[attr-defined]
|
||||
params[i] = param.replace(annotation=type_comment.argtypes[i]) # type: ignore[attr-defined]
|
||||
|
||||
if node.returns:
|
||||
return Signature(params, return_annotation=node.returns)
|
||||
@ -93,19 +109,15 @@ def get_type_comment(obj: Any, bound_method: bool = False) -> Signature | None:
|
||||
# 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, type_comments=True)
|
||||
subject = cast(
|
||||
'ast.FunctionDef', module.body[0].body[0], # type: ignore[attr-defined]
|
||||
)
|
||||
subject = cast('ast.FunctionDef', module.body[0].body[0]) # type: ignore[attr-defined]
|
||||
else:
|
||||
module = ast.parse(source, type_comments=True)
|
||||
subject = cast('ast.FunctionDef', module.body[0])
|
||||
|
||||
type_comment = getattr(subject, "type_comment", None)
|
||||
type_comment = getattr(subject, 'type_comment', None)
|
||||
if type_comment:
|
||||
function = ast.parse(type_comment, mode='func_type', type_comments=True)
|
||||
return signature_from_ast(
|
||||
subject, bound_method, function, # type: ignore[arg-type]
|
||||
)
|
||||
return signature_from_ast(subject, bound_method, function) # type: ignore[arg-type]
|
||||
else:
|
||||
return None
|
||||
except (OSError, TypeError): # failed to load source code
|
||||
@ -114,7 +126,9 @@ def get_type_comment(obj: Any, bound_method: bool = False) -> Signature | None:
|
||||
return None
|
||||
|
||||
|
||||
def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method: bool) -> None:
|
||||
def update_annotations_using_type_comments(
|
||||
app: Sphinx, obj: Any, bound_method: bool
|
||||
) -> None:
|
||||
"""Update annotations info of *obj* using type_comments."""
|
||||
try:
|
||||
type_sig = get_type_comment(obj, bound_method)
|
||||
@ -129,13 +143,16 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method:
|
||||
if 'return' not in obj.__annotations__:
|
||||
obj.__annotations__['return'] = type_sig.return_annotation
|
||||
except KeyError as exc:
|
||||
logger.warning(__("Failed to update signature for %r: parameter not found: %s"),
|
||||
obj, exc)
|
||||
logger.warning(
|
||||
__('Failed to update signature for %r: parameter not found: %s'), obj, exc
|
||||
)
|
||||
except NotImplementedError as exc: # failed to ast.unparse()
|
||||
logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc)
|
||||
logger.warning(__('Failed to parse type_comment for %r: %s'), obj, exc)
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.connect('autodoc-before-process-signature', update_annotations_using_type_comments)
|
||||
app.connect(
|
||||
'autodoc-before-process-signature', update_annotations_using_type_comments
|
||||
)
|
||||
|
||||
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
||||
|
@ -21,8 +21,15 @@ if TYPE_CHECKING:
|
||||
from sphinx.ext.autodoc import Options
|
||||
|
||||
|
||||
def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
options: Options, args: str, retann: str) -> None:
|
||||
def record_typehints(
|
||||
app: Sphinx,
|
||||
objtype: str,
|
||||
name: str,
|
||||
obj: Any,
|
||||
options: Options,
|
||||
args: str,
|
||||
retann: str,
|
||||
) -> None:
|
||||
"""Record type hints to env object."""
|
||||
if app.config.autodoc_typehints_format == 'short':
|
||||
mode = 'smart'
|
||||
@ -36,14 +43,19 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||
sig = inspect.signature(obj, type_aliases=app.config.autodoc_type_aliases)
|
||||
for param in sig.parameters.values():
|
||||
if param.annotation is not param.empty:
|
||||
annotation[param.name] = stringify_annotation(param.annotation, mode) # type: ignore[arg-type]
|
||||
annotation[param.name] = stringify_annotation(
|
||||
param.annotation,
|
||||
mode, # type: ignore[arg-type]
|
||||
)
|
||||
if sig.return_annotation is not sig.empty:
|
||||
annotation['return'] = stringify_annotation(sig.return_annotation, mode) # type: ignore[arg-type]
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None:
|
||||
def merge_typehints(
|
||||
app: Sphinx, domain: str, objtype: str, contentnode: Element
|
||||
) -> None:
|
||||
if domain != 'py':
|
||||
return
|
||||
if app.config.autodoc_typehints not in {'both', 'description'}:
|
||||
@ -67,18 +79,20 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element
|
||||
field_lists.append(field_list)
|
||||
|
||||
for field_list in field_lists:
|
||||
if app.config.autodoc_typehints_description_target == "all":
|
||||
if app.config.autodoc_typehints_description_target == 'all':
|
||||
if objtype == 'class':
|
||||
modify_field_list(field_list, annotations[fullname], suppress_rtype=True)
|
||||
modify_field_list(
|
||||
field_list, annotations[fullname], suppress_rtype=True
|
||||
)
|
||||
else:
|
||||
modify_field_list(field_list, annotations[fullname])
|
||||
elif app.config.autodoc_typehints_description_target == "documented_params":
|
||||
elif app.config.autodoc_typehints_description_target == 'documented_params':
|
||||
augment_descriptions_with_types(
|
||||
field_list, annotations[fullname], force_rtype=True,
|
||||
field_list, annotations[fullname], force_rtype=True
|
||||
)
|
||||
else:
|
||||
augment_descriptions_with_types(
|
||||
field_list, annotations[fullname], force_rtype=False,
|
||||
field_list, annotations[fullname], force_rtype=False
|
||||
)
|
||||
|
||||
|
||||
@ -95,8 +109,9 @@ def insert_field_list(node: Element) -> nodes.field_list:
|
||||
return field_list
|
||||
|
||||
|
||||
def modify_field_list(node: nodes.field_list, annotations: dict[str, str],
|
||||
suppress_rtype: bool = False) -> None:
|
||||
def modify_field_list(
|
||||
node: nodes.field_list, annotations: dict[str, str], suppress_rtype: bool = False
|
||||
) -> None:
|
||||
arguments: dict[str, dict[str, bool]] = {}
|
||||
fields = cast('Iterable[nodes.field]', node)
|
||||
for field in fields:
|
||||
@ -202,8 +217,9 @@ def augment_descriptions_with_types(
|
||||
# Add 'rtype' if 'return' is present and 'rtype' isn't.
|
||||
if 'return' in annotations:
|
||||
rtype = annotations['return']
|
||||
if 'return' not in has_type and ('return' in has_description or
|
||||
(force_rtype and rtype != "None")):
|
||||
if 'return' not in has_type and (
|
||||
'return' in has_description or (force_rtype and rtype != 'None')
|
||||
):
|
||||
field = nodes.field()
|
||||
field += nodes.field_name('', 'rtype')
|
||||
field += nodes.field_body('', nodes.paragraph('', rtype))
|
||||
|
Loading…
Reference in New Issue
Block a user