mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-14 17:32:12 -05:00
Improve various autodoc tests (#13958)
This commit is contained in:
1 parent
e281d26b5e
commit
a8f2b9629b
1 file changed
+167
-134
@@ -27,10 +27,9 @@ from sphinx.ext.autodoc._property_types import (
|
||||
_ItemProperties,
|
||||
)
|
||||
from sphinx.ext.autodoc._sentinels import ALL
|
||||
|
||||
# NEVER import these objects from sphinx.ext.autodoc directly
|
||||
from sphinx.ext.autodoc.directive import DocumenterBridge
|
||||
from sphinx.ext.autodoc.importer import _format_signatures, _parse_name
|
||||
from sphinx.util.inspect import safe_getattr
|
||||
|
||||
from tests.test_ext_autodoc.autodoc_util import do_autodoc
|
||||
|
||||
@@ -133,74 +132,79 @@ def test_parse_name(app):
|
||||
assert parsed == ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)
|
||||
|
||||
|
||||
def format_sig(obj_type, name, obj, *, app, args=None, retann=None):
|
||||
def get_attr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
return autodoc_attrgetter(obj, name, *defargs, registry=app.registry)
|
||||
|
||||
options = _AutoDocumenterOptions(
|
||||
synopsis='',
|
||||
platform='',
|
||||
members=[],
|
||||
member_order='alphabetical',
|
||||
exclude_members=set(),
|
||||
)
|
||||
|
||||
parent = object # dummy
|
||||
props = _ClassDefProperties(
|
||||
obj_type=obj_type,
|
||||
module_name='',
|
||||
parts=(name,),
|
||||
docstring_lines=(),
|
||||
bases=getattr(obj, '__bases__', None),
|
||||
_obj=obj,
|
||||
_obj___module__=getattr(obj, '__module__', None),
|
||||
_obj___qualname__=getattr(obj, '__qualname__', None),
|
||||
_obj___name__=name,
|
||||
_obj_bases=(),
|
||||
_obj_is_new_type=False,
|
||||
_obj_is_typevar=False,
|
||||
)
|
||||
props._docstrings = _get_docstring_lines(
|
||||
props,
|
||||
class_doc_from=app.config.autoclass_content,
|
||||
get_attr=get_attr,
|
||||
inherit_docstrings=app.config.autodoc_inherit_docstrings,
|
||||
parent=parent,
|
||||
tab_width=8,
|
||||
)
|
||||
signatures = _format_signatures(
|
||||
config=app.config,
|
||||
events=app.events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
parent=parent,
|
||||
props=props,
|
||||
args=args,
|
||||
retann=retann,
|
||||
)
|
||||
if not signatures:
|
||||
return ()
|
||||
assert len(signatures) == 1
|
||||
return signatures[0]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='root')
|
||||
def test_format_signature(app):
|
||||
def process_signature(app, what, name, obj, options, args, retann):
|
||||
def test_format_signatures(app):
|
||||
def process_signature(_app, what, name, _obj, _options, _args, _retann):
|
||||
processed_signatures.append((what, name))
|
||||
if name == '.bar':
|
||||
return '42', None
|
||||
return None
|
||||
|
||||
def skip_member(app, what, name, obj, skip, options):
|
||||
def skip_member(_app, _what, name, _obj, skip, _options):
|
||||
if name in {'__special1__', '__special2__'}:
|
||||
return skip
|
||||
if name.startswith('__'):
|
||||
return True
|
||||
if name == 'skipmeth':
|
||||
if name == ('skipmeth', ''):
|
||||
return True
|
||||
return None
|
||||
|
||||
app.connect('autodoc-process-signature', process_signature)
|
||||
app.connect('autodoc-skip-member', skip_member)
|
||||
|
||||
def get_attr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
return autodoc_attrgetter(obj, name, *defargs, registry=app.registry)
|
||||
|
||||
options = make_directive_bridge(app.env).genopt
|
||||
|
||||
def formatsig(objtype, name, obj, args, retann):
|
||||
parent = object # dummy
|
||||
props = _ClassDefProperties(
|
||||
obj_type=objtype,
|
||||
module_name='',
|
||||
parts=(name,),
|
||||
docstring_lines=(),
|
||||
bases=getattr(obj, '__bases__', None),
|
||||
_obj=obj,
|
||||
_obj___module__=getattr(obj, '__module__', None),
|
||||
_obj___qualname__=getattr(obj, '__qualname__', None),
|
||||
_obj___name__=name,
|
||||
_obj_bases=(),
|
||||
_obj_is_new_type=False,
|
||||
_obj_is_typevar=False,
|
||||
)
|
||||
props._docstrings = _get_docstring_lines(
|
||||
props,
|
||||
class_doc_from=app.config.autoclass_content,
|
||||
get_attr=get_attr,
|
||||
inherit_docstrings=app.config.autodoc_inherit_docstrings,
|
||||
parent=parent,
|
||||
tab_width=8,
|
||||
)
|
||||
signatures = _format_signatures(
|
||||
config=app.config,
|
||||
events=app.events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
parent=parent,
|
||||
props=props,
|
||||
args=args,
|
||||
retann=retann,
|
||||
)
|
||||
res = '\n'.join(
|
||||
f'{args} -> {retann}' if retann else str(args)
|
||||
for args, retann in signatures
|
||||
)
|
||||
print(res)
|
||||
return res
|
||||
|
||||
# no signatures for modules
|
||||
assert formatsig('module', 'test', None, None, None) == ''
|
||||
assert format_sig('module', 'test', None, app=app) == ()
|
||||
|
||||
# test for functions
|
||||
def f(a, b, c=1, **d):
|
||||
@@ -209,9 +213,12 @@ def test_format_signature(app):
|
||||
def g(a='\n'):
|
||||
pass
|
||||
|
||||
assert formatsig('function', 'f', f, None, None) == '(a, b, c=1, **d)'
|
||||
assert formatsig('function', 'f', f, '(a, b, c, d)', None) == '(a, b, c, d)'
|
||||
assert formatsig('function', 'g', g, None, None) == r"(a='\n')"
|
||||
assert format_sig('function', 'f', f, app=app) == ('(a, b, c=1, **d)', '')
|
||||
assert format_sig('function', 'f', f, app=app, args='(a, b, c, d)') == (
|
||||
'(a, b, c, d)',
|
||||
'',
|
||||
)
|
||||
assert format_sig('function', 'g', g, app=app) == (r"(a='\n')", '')
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
for params, expect in [
|
||||
@@ -222,15 +229,25 @@ def test_format_signature(app):
|
||||
ns = {}
|
||||
exec(f'def f[T]{params}: pass', ns) # NoQA: S102
|
||||
f = ns['f']
|
||||
assert formatsig('function', 'f', f, None, None) == expect
|
||||
assert formatsig('function', 'f', f, '(...)', None) == '(...)'
|
||||
assert formatsig('function', 'f', f, '(...)', '...') == '(...) -> ...'
|
||||
assert format_sig('function', 'f', f, app=app) == (expect, '')
|
||||
assert format_sig('function', 'f', f, app=app, args='(...)') == (
|
||||
'(...)',
|
||||
'',
|
||||
)
|
||||
assert format_sig(
|
||||
'function', 'f', f, app=app, args='(...)', retann='...'
|
||||
) == ('(...)', '...')
|
||||
|
||||
exec(f'def f[T]{params} -> list[T]: return []', ns) # NoQA: S102
|
||||
f = ns['f']
|
||||
assert formatsig('function', 'f', f, None, None) == f'{expect} -> list[T]'
|
||||
assert formatsig('function', 'f', f, '(...)', None) == '(...)'
|
||||
assert formatsig('function', 'f', f, '(...)', '...') == '(...) -> ...'
|
||||
assert format_sig('function', 'f', f, app=app) == (expect, 'list[T]')
|
||||
assert format_sig('function', 'f', f, app=app, args='(...)') == (
|
||||
'(...)',
|
||||
'',
|
||||
)
|
||||
assert format_sig(
|
||||
'function', 'f', f, app=app, args='(...)', retann='...'
|
||||
) == ('(...)', '...')
|
||||
|
||||
# TODO(picnixz): add more test cases for PEP-695 classes as well (though
|
||||
# complex cases are less likely to appear and are painful to test).
|
||||
@@ -245,7 +262,7 @@ def test_format_signature(app):
|
||||
|
||||
# an empty init and no init are the same
|
||||
for C in (D, E):
|
||||
assert formatsig('class', 'D', C, None, None) == '()'
|
||||
assert format_sig('class', 'D', C, app=app) == ('()', '')
|
||||
|
||||
class SomeMeta(type):
|
||||
def __call__(cls, a, b=None):
|
||||
@@ -275,24 +292,30 @@ def test_format_signature(app):
|
||||
|
||||
# subclasses inherit
|
||||
for C in (F, FNew, FMeta, G, GNew, GMeta):
|
||||
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
|
||||
assert formatsig('class', 'C', D, '(a, b)', 'X') == '(a, b) -> X'
|
||||
assert format_sig('class', 'C', C, app=app) == ('(a, b=None)', '')
|
||||
assert format_sig('class', 'C', D, app=app, args='(a, b)', retann='X') == (
|
||||
'(a, b)',
|
||||
'X',
|
||||
)
|
||||
|
||||
class ListSubclass(list): # NoQA: FURB189
|
||||
pass
|
||||
|
||||
# only supported if the python implementation decides to document it
|
||||
if getattr(list, '__text_signature__', None) is not None:
|
||||
assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
|
||||
assert format_sig('class', 'C', ListSubclass, app=app) == (
|
||||
'(iterable=(), /)',
|
||||
'',
|
||||
)
|
||||
else:
|
||||
assert formatsig('class', 'C', ListSubclass, None, None) == ''
|
||||
assert format_sig('class', 'C', ListSubclass, app=app) == ()
|
||||
|
||||
class ExceptionSubclass(Exception):
|
||||
pass
|
||||
|
||||
# Exception has no __text_signature__ at least in Python 3.11
|
||||
if getattr(Exception, '__text_signature__', None) is None:
|
||||
assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
|
||||
assert format_sig('class', 'C', ExceptionSubclass, app=app) == ()
|
||||
|
||||
# __init__ have signature at first line of docstring
|
||||
app.config.autoclass_content = 'both'
|
||||
@@ -310,8 +333,14 @@ def test_format_signature(app):
|
||||
class G2(F2):
|
||||
pass
|
||||
|
||||
assert formatsig('class', 'F2', F2, None, None) == '(a1, a2, kw1=True, kw2=False)'
|
||||
assert formatsig('class', 'G2', G2, None, None) == '(a1, a2, kw1=True, kw2=False)'
|
||||
assert format_sig('class', 'F2', F2, app=app) == (
|
||||
'(a1, a2, kw1=True, kw2=False)',
|
||||
'',
|
||||
)
|
||||
assert format_sig('class', 'G2', G2, app=app) == (
|
||||
'(a1, a2, kw1=True, kw2=False)',
|
||||
'',
|
||||
)
|
||||
|
||||
# test for methods
|
||||
class H:
|
||||
@@ -324,35 +353,36 @@ def test_format_signature(app):
|
||||
def foo3(self, d='\n'):
|
||||
pass
|
||||
|
||||
assert formatsig('method', 'H.foo', H.foo1, None, None) == '(b, *c)'
|
||||
assert formatsig('method', 'H.foo', H.foo1, '(a)', None) == '(a)'
|
||||
assert formatsig('method', 'H.foo', H.foo2, None, None) == '(*c)'
|
||||
assert formatsig('method', 'H.foo', H.foo3, None, None) == r"(d='\n')"
|
||||
assert format_sig('method', 'H.foo', H.foo1, app=app) == ('(b, *c)', '')
|
||||
assert format_sig('method', 'H.foo', H.foo1, app=app, args='(a)') == ('(a)', '')
|
||||
assert format_sig('method', 'H.foo', H.foo2, app=app) == ('(*c)', '')
|
||||
assert format_sig('method', 'H.foo', H.foo3, app=app) == (r"(d='\n')", '')
|
||||
|
||||
# test bound methods interpreted as functions
|
||||
assert formatsig('function', 'foo', H().foo1, None, None) == '(b, *c)'
|
||||
assert formatsig('function', 'foo', H().foo2, None, None) == '(*c)'
|
||||
assert formatsig('function', 'foo', H().foo3, None, None) == r"(d='\n')"
|
||||
assert format_sig('function', 'foo', H().foo1, app=app) == ('(b, *c)', '')
|
||||
assert format_sig('function', 'foo', H().foo2, app=app) == ('(*c)', '')
|
||||
assert format_sig('function', 'foo', H().foo3, app=app) == (r"(d='\n')", '')
|
||||
|
||||
# test exception handling (exception is caught and args is '')
|
||||
app.config.autodoc_docstring_signature = False
|
||||
assert formatsig('function', 'int', int, None, None) == ''
|
||||
assert format_sig('function', 'int', int, app=app) == ()
|
||||
|
||||
# test processing by event handler
|
||||
assert formatsig('method', 'bar', H.foo1, None, None) == '42'
|
||||
assert format_sig('method', 'bar', H.foo1, app=app) == ('42', '')
|
||||
|
||||
# test functions created via functools.partial
|
||||
from functools import partial
|
||||
|
||||
curried1 = partial(lambda a, b, c: None, 'A')
|
||||
assert formatsig('function', 'curried1', curried1, None, None) == '(b, c)'
|
||||
assert format_sig('function', 'curried1', curried1, app=app) == ('(b, c)', '')
|
||||
curried2 = partial(lambda a, b, c=42: None, 'A')
|
||||
assert formatsig('function', 'curried2', curried2, None, None) == '(b, c=42)'
|
||||
assert format_sig('function', 'curried2', curried2, app=app) == ('(b, c=42)', '')
|
||||
curried3 = partial(lambda a, b, *c: None, 'A')
|
||||
assert formatsig('function', 'curried3', curried3, None, None) == '(b, *c)'
|
||||
assert format_sig('function', 'curried3', curried3, app=app) == ('(b, *c)', '')
|
||||
curried4 = partial(lambda a, b, c=42, *d, **e: None, 'A')
|
||||
assert (
|
||||
formatsig('function', 'curried4', curried4, None, None) == '(b, c=42, *d, **e)'
|
||||
assert format_sig('function', 'curried4', curried4, app=app) == (
|
||||
'(b, c=42, *d, **e)',
|
||||
'',
|
||||
)
|
||||
|
||||
|
||||
@@ -397,7 +427,13 @@ def test_autodoc_process_signature_typehints(app):
|
||||
def get_attr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
return autodoc_attrgetter(obj, name, *defargs, registry=app.registry)
|
||||
|
||||
options = make_directive_bridge(app.env).genopt
|
||||
options = _AutoDocumenterOptions(
|
||||
synopsis='',
|
||||
platform='',
|
||||
members=[],
|
||||
member_order='alphabetical',
|
||||
exclude_members=set(),
|
||||
)
|
||||
_format_signatures(
|
||||
config=app.config,
|
||||
events=app.events,
|
||||
@@ -411,62 +447,53 @@ def test_autodoc_process_signature_typehints(app):
|
||||
]
|
||||
|
||||
|
||||
def get_docstring_lines(obj_type, obj, *, config):
|
||||
parent = object # dummy
|
||||
props = _ItemProperties(
|
||||
obj_type=obj_type,
|
||||
module_name='',
|
||||
parts=(obj.__name__,),
|
||||
docstring_lines=(),
|
||||
_obj=obj,
|
||||
_obj___module__=getattr(obj, '__module__', None),
|
||||
)
|
||||
ds = _get_docstring_lines(
|
||||
props,
|
||||
class_doc_from=config.autoclass_content,
|
||||
get_attr=safe_getattr,
|
||||
inherit_docstrings=config.autodoc_inherit_docstrings,
|
||||
parent=parent,
|
||||
tab_width=8,
|
||||
)
|
||||
# for testing purposes, concat them and strip the empty line at the end
|
||||
res = list(itertools.chain.from_iterable(ds or ()))
|
||||
if res:
|
||||
res.pop()
|
||||
return tuple(res)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='root')
|
||||
def test_get_doc(app):
|
||||
def get_attr(obj: Any, name: str, *defargs: Any) -> Any:
|
||||
return autodoc_attrgetter(obj, name, *defargs, registry=app.registry)
|
||||
|
||||
options = make_directive_bridge(app.env).genopt
|
||||
|
||||
def getdocl(objtype, obj):
|
||||
parent = object # dummy
|
||||
props = _ItemProperties(
|
||||
obj_type=objtype,
|
||||
module_name='',
|
||||
parts=(obj.__name__,),
|
||||
docstring_lines=(),
|
||||
_obj=obj,
|
||||
_obj___module__=getattr(obj, '__module__', None),
|
||||
)
|
||||
props._docstrings = _get_docstring_lines(
|
||||
props,
|
||||
class_doc_from=app.config.autoclass_content,
|
||||
get_attr=get_attr,
|
||||
inherit_docstrings=app.config.autodoc_inherit_docstrings,
|
||||
parent=parent,
|
||||
tab_width=8,
|
||||
)
|
||||
_format_signatures( # handle docstring signatures!
|
||||
config=app.config,
|
||||
events=app.events,
|
||||
get_attr=get_attr,
|
||||
options=options,
|
||||
parent=parent,
|
||||
props=props,
|
||||
)
|
||||
ds = props._docstrings
|
||||
# for testing purposes, concat them and strip the empty line at the end
|
||||
res = list(itertools.chain.from_iterable(ds))[:-1]
|
||||
print(res)
|
||||
return res
|
||||
def test_get_docstring_lines(app):
|
||||
config = app.config
|
||||
|
||||
# objects without docstring
|
||||
def f():
|
||||
pass
|
||||
|
||||
assert getdocl('function', f) == []
|
||||
assert get_docstring_lines('function', f, config=config) == ()
|
||||
|
||||
# standard function, diverse docstring styles...
|
||||
def f():
|
||||
"""Docstring"""
|
||||
|
||||
def g():
|
||||
assert get_docstring_lines('function', f, config=config) == ('Docstring',)
|
||||
|
||||
def f():
|
||||
"""
|
||||
Docstring
|
||||
""" # NoQA: D212
|
||||
|
||||
for func in (f, g):
|
||||
assert getdocl('function', func) == ['Docstring']
|
||||
assert get_docstring_lines('function', f, config=config) == ('Docstring',)
|
||||
|
||||
# first line vs. other lines indentation
|
||||
def f():
|
||||
@@ -476,13 +503,18 @@ def test_get_doc(app):
|
||||
lines
|
||||
"""
|
||||
|
||||
assert getdocl('function', f) == ['First line', '', 'Other', ' lines']
|
||||
assert get_docstring_lines('function', f, config=config) == (
|
||||
'First line',
|
||||
'',
|
||||
'Other',
|
||||
' lines',
|
||||
)
|
||||
|
||||
# charset guessing (this module is encoded in utf-8)
|
||||
def f():
|
||||
"""Döcstring"""
|
||||
|
||||
assert getdocl('function', f) == ['Döcstring']
|
||||
assert get_docstring_lines('function', f, config=config) == ('Döcstring',)
|
||||
|
||||
# verify that method docstrings get extracted in both normal case
|
||||
# and in case of bound method posing as a function
|
||||
@@ -490,8 +522,9 @@ def test_get_doc(app):
|
||||
def foo(self):
|
||||
"""Method docstring"""
|
||||
|
||||
assert getdocl('method', J.foo) == ['Method docstring']
|
||||
assert getdocl('function', J().foo) == ['Method docstring']
|
||||
expected = ('Method docstring',)
|
||||
assert get_docstring_lines('method', J.foo, config=config) == expected
|
||||
assert get_docstring_lines('function', J().foo, config=config) == expected
|
||||
|
||||
|
||||
class _MyDocumenter(ModuleLevelDocumenter):
|
||||
@@ -539,21 +572,21 @@ def test_attrgetter_using(app):
|
||||
|
||||
options.inherited_members = inherited_members_option(False)
|
||||
with catch_warnings(record=True):
|
||||
_assert_getter_works(app, directive, 'class', 'target.Class', ['meth'])
|
||||
_assert_getter_works(app, directive, 'class', 'target.Class', {'meth'})
|
||||
|
||||
options.inherited_members = inherited_members_option(True)
|
||||
with catch_warnings(record=True):
|
||||
_assert_getter_works(
|
||||
app, directive, 'class', 'target.inheritance.Derived', ['inheritedmeth']
|
||||
app, directive, 'class', 'target.inheritance.Derived', {'inheritedmeth'}
|
||||
)
|
||||
|
||||
|
||||
def _assert_getter_works(app, directive, objtype, name, attrs=(), **kw):
|
||||
getattr_spy = []
|
||||
getattr_spy = set()
|
||||
|
||||
def _special_getattr(obj, attr_name, *defargs):
|
||||
if attr_name in attrs:
|
||||
getattr_spy.append((obj, attr_name))
|
||||
getattr_spy.add((obj, attr_name))
|
||||
return None
|
||||
return getattr(obj, attr_name, *defargs)
|
||||
|
||||
|
||||
Reference in new issue
Block a user