diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:25:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:25:40 +0000 |
commit | cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a (patch) | |
tree | 18dcde1a8d1f5570a77cd0c361de3b490d02c789 /tests/test_ext_autodoc.py | |
parent | Initial commit. (diff) | |
download | sphinx-cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a.tar.xz sphinx-cf7da1843c45a4c2df7a749f7886a2d2ba0ee92a.zip |
Adding upstream version 7.2.6.upstream/7.2.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/test_ext_autodoc.py')
-rw-r--r-- | tests/test_ext_autodoc.py | 2537 |
1 files changed, 2537 insertions, 0 deletions
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py new file mode 100644 index 0000000..7062763 --- /dev/null +++ b/tests/test_ext_autodoc.py @@ -0,0 +1,2537 @@ +"""Test the autodoc extension. + +This tests mainly the Documenters; the auto directives are tested in a test +source file translated by test_build. +""" + +import sys +from types import SimpleNamespace +from unittest.mock import Mock +from warnings import catch_warnings + +import pytest +from docutils.statemachine import ViewList + +from sphinx import addnodes +from sphinx.ext.autodoc import ALL, ModuleLevelDocumenter, Options +from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options +from sphinx.util.docutils import LoggingReporter + +try: + # Enable pyximport to test cython module + import pyximport + pyximport.install() +except ImportError: + pyximport = None + + +def do_autodoc(app, objtype, name, options=None): + if options is None: + options = {} + app.env.temp_data.setdefault('docname', 'index') # set dummy docname + doccls = app.registry.documenters[objtype] + docoptions = process_documenter_options(doccls, app.config, options) + state = Mock() + state.document.settings.tab_width = 8 + bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state) + documenter = doccls(bridge, name) + documenter.generate() + + return bridge.result + + +def make_directive_bridge(env): + options = Options( + inherited_members = False, + undoc_members = False, + private_members = False, + special_members = False, + imported_members = False, + show_inheritance = False, + no_index = False, + annotation = None, + synopsis = '', + platform = '', + deprecated = False, + members = [], + member_order = 'alphabetical', + exclude_members = set(), + ignore_module_all = False, + ) + + directive = SimpleNamespace( + env = env, + genopt = options, + result = ViewList(), + record_dependencies = set(), + state = Mock(), + ) + directive.state.document.settings.tab_width = 8 + + return directive + + +processed_signatures = [] + + +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): + if name in ('__special1__', '__special2__'): + return skip + if name.startswith('__'): + return True + if name == 'skipmeth': + return True + return None + + +def test_parse_name(app): + def verify(objtype, name, result): + inst = app.registry.documenters[objtype](directive, name) + assert inst.parse_name() + assert (inst.modname, inst.objpath, inst.args, inst.retann) == result + + directive = make_directive_bridge(app.env) + + # for modules + verify('module', 'test_ext_autodoc', ('test_ext_autodoc', [], None, None)) + verify('module', 'test.test_ext_autodoc', ('test.test_ext_autodoc', [], None, None)) + verify('module', 'test(arg)', ('test', [], 'arg', None)) + assert 'signature arguments' in app._warning.getvalue() + + # for functions/classes + verify('function', 'test_ext_autodoc.raises', + ('test_ext_autodoc', ['raises'], None, None)) + verify('function', 'test_ext_autodoc.raises(exc) -> None', + ('test_ext_autodoc', ['raises'], 'exc', 'None')) + directive.env.temp_data['autodoc:module'] = 'test_ext_autodoc' + verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None)) + del directive.env.temp_data['autodoc:module'] + directive.env.ref_context['py:module'] = 'test_ext_autodoc' + verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None)) + verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) + + # for members + directive.env.ref_context['py:module'] = 'sphinx.testing.util' + verify('method', 'SphinxTestApp.cleanup', + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) + directive.env.ref_context['py:module'] = 'sphinx.testing.util' + directive.env.ref_context['py:class'] = 'Foo' + directive.env.temp_data['autodoc:class'] = 'SphinxTestApp' + verify('method', 'cleanup', + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) + verify('method', 'SphinxTestApp.cleanup', + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) + + +def test_format_signature(app): + app.connect('autodoc-process-signature', process_signature) + app.connect('autodoc-skip-member', skip_member) + + directive = make_directive_bridge(app.env) + + def formatsig(objtype, name, obj, args, retann): + inst = app.registry.documenters[objtype](directive, name) + inst.fullname = name + inst.doc_as_attr = False # for class objtype + inst.parent = object # dummy + inst.object = obj + inst.objpath = [name] + inst.args = args + inst.retann = retann + res = inst.format_signature() + print(res) + return res + + # no signatures for modules + assert formatsig('module', 'test', None, None, None) == '' + + # test for functions + def f(a, b, c=1, **d): + pass + + 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')" + + # test for classes + class D: + pass + + class E: + def __init__(self): + pass + + # an empty init and no init are the same + for C in (D, E): + assert formatsig('class', 'D', C, None, None) == '()' + + class SomeMeta(type): + def __call__(cls, a, b=None): + return type.__call__(cls, a, b) + + # these three are all equivalent + class F: + def __init__(self, a, b=None): + pass + + class FNew: + def __new__(cls, a, b=None): + return super().__new__(cls) + + class FMeta(metaclass=SomeMeta): + pass + + # and subclasses should always inherit + class G(F): + pass + + class GNew(FNew): + pass + + class GMeta(FMeta): + pass + + # 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' + + class ListSubclass(list): + 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=(), /)' + else: + assert formatsig('class', 'C', ListSubclass, None, None) == '' + + 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) == '' + + # __init__ have signature at first line of docstring + directive.env.config.autoclass_content = 'both' + + class F2: + """some docstring for F2.""" + def __init__(self, *args, **kw): + """ + __init__(a1, a2, kw1=True, kw2=False) + + some docstring for __init__. + """ + 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)' + + # test for methods + class H: + def foo1(self, b, *c): + pass + + def foo2(b, *c): + pass + + 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')" + + # 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')" + + # test exception handling (exception is caught and args is '') + directive.env.config.autodoc_docstring_signature = False + assert formatsig('function', 'int', int, None, None) == '' + + # test processing by event handler + assert formatsig('method', 'bar', H.foo1, None, None) == '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)' + curried2 = partial(lambda a, b, c=42: None, 'A') + assert formatsig('function', 'curried2', curried2, None, None) == \ + '(b, c=42)' + curried3 = partial(lambda a, b, *c: None, 'A') + assert formatsig('function', 'curried3', curried3, None, None) == \ + '(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)' + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_process_signature_typing_generic(app): + actual = do_autodoc(app, 'class', 'target.generic_class.A', {}) + + assert list(actual) == [ + '', + '.. py:class:: A(a, b=None)', + ' :module: target.generic_class', + '', + ' docstring for A', + '', + ] + + +def test_autodoc_process_signature_typehints(app): + captured = [] + + def process_signature(*args): + captured.append(args) + + app.connect('autodoc-process-signature', process_signature) + + def func(x: int, y: int) -> int: + pass + + directive = make_directive_bridge(app.env) + inst = app.registry.documenters['function'](directive, 'func') + inst.fullname = 'func' + inst.object = func + inst.objpath = ['func'] + inst.format_signature() + assert captured == [(app, 'function', 'func', func, + directive.genopt, '(x: int, y: int)', 'int')] + + +def test_get_doc(app): + directive = make_directive_bridge(app.env) + + def getdocl(objtype, obj): + inst = app.registry.documenters[objtype](directive, 'tmp') + inst.parent = object # dummy + inst.object = obj + inst.objpath = [obj.__name__] + inst.doc_as_attr = False + inst.format_signature() # handle docstring signatures! + ds = inst.get_doc() + # for testing purposes, concat them and strip the empty line at the end + res = sum(ds, [])[:-1] + print(res) + return res + + # objects without docstring + def f(): + pass + assert getdocl('function', f) == [] + + # standard function, diverse docstring styles... + def f(): + """Docstring""" + def g(): + """ + Docstring + """ + for func in (f, g): + assert getdocl('function', func) == ['Docstring'] + + # first line vs. other lines indentation + def f(): + """First line + + Other + lines + """ + assert getdocl('function', f) == ['First line', '', 'Other', ' lines'] + + # charset guessing (this module is encoded in utf-8) + def f(): + """Döcstring""" + assert getdocl('function', f) == ['Döcstring'] + + # verify that method docstrings get extracted in both normal case + # and in case of bound method posing as a function + class J: + def foo(self): + """Method docstring""" + assert getdocl('method', J.foo) == ['Method docstring'] + assert getdocl('function', J().foo) == ['Method docstring'] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_new_documenter(app): + class MyDocumenter(ModuleLevelDocumenter): + objtype = 'integer' + directivetype = 'integer' + priority = 100 + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + return isinstance(member, int) + + def document_members(self, all_members=False): + return + + app.add_autodocumenter(MyDocumenter) + + options = {"members": 'integer'} + actual = do_autodoc(app, 'module', 'target', options) + assert list(actual) == [ + '', + '.. py:module:: target', + '', + '', + '.. py:integer:: integer', + ' :module: target', + '', + ' documentation for the integer', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_attrgetter_using(app): + directive = make_directive_bridge(app.env) + directive.genopt['members'] = ALL + + directive.genopt['inherited_members'] = False + with catch_warnings(record=True): + _assert_getter_works(app, directive, 'class', 'target.Class', ['meth']) + + directive.genopt['inherited_members'] = True + with catch_warnings(record=True): + _assert_getter_works(app, directive, 'class', 'target.inheritance.Derived', ['inheritedmeth']) + + +def _assert_getter_works(app, directive, objtype, name, attrs=(), **kw): + getattr_spy = [] + + def _special_getattr(obj, attr_name, *defargs): + if attr_name in attrs: + getattr_spy.append((obj, attr_name)) + return None + return getattr(obj, attr_name, *defargs) + + app.add_autodoc_attrgetter(type, _special_getattr) + + getattr_spy.clear() + app.registry.documenters[objtype](directive, name).generate(**kw) + + hooked_members = {s[1] for s in getattr_spy} + documented_members = {s[1] for s in processed_signatures} + for attr in attrs: + fullname = '.'.join((name, attr)) + assert attr in hooked_members + assert fullname not in documented_members, f'{fullname!r} not intercepted' + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_py_module(app, warning): + # without py:module + actual = do_autodoc(app, 'method', 'Class.meth') + assert list(actual) == [] + assert ("don't know which module to import for autodocumenting 'Class.meth'" + in warning.getvalue()) + + # with py:module + app.env.ref_context['py:module'] = 'target' + warning.truncate(0) + + actual = do_autodoc(app, 'method', 'Class.meth') + assert list(actual) == [ + '', + '.. py:method:: Class.meth()', + ' :module: target', + '', + ' Function.', + '', + ] + assert ("don't know which module to import for autodocumenting 'Class.meth'" + not in warning.getvalue()) + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_decorator(app): + actual = do_autodoc(app, 'decorator', 'target.decorator.deco1') + assert list(actual) == [ + '', + '.. py:decorator:: deco1', + ' :module: target.decorator', + '', + ' docstring for deco1', + '', + ] + + actual = do_autodoc(app, 'decorator', 'target.decorator.deco2') + assert list(actual) == [ + '', + '.. py:decorator:: deco2(condition, message)', + ' :module: target.decorator', + '', + ' docstring for deco2', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_exception(app): + actual = do_autodoc(app, 'exception', 'target.CustomEx') + assert list(actual) == [ + '', + '.. py:exception:: CustomEx', + ' :module: target', + '', + ' My custom exception.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_warnings(app, warning): + app.env.temp_data['docname'] = 'dummy' + + # can't import module + do_autodoc(app, 'module', 'unknown') + assert "failed to import module 'unknown'" in warning.getvalue() + + # missing function + do_autodoc(app, 'function', 'unknown') + assert "import for autodocumenting 'unknown'" in warning.getvalue() + + do_autodoc(app, 'function', 'target.unknown') + assert "failed to import function 'unknown' from module 'target'" in warning.getvalue() + + # missing method + do_autodoc(app, 'method', 'target.Class.unknown') + assert "failed to import method 'Class.unknown' from module 'target'" in warning.getvalue() + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_attributes(app): + options = {"synopsis": 'Synopsis', + "platform": "Platform", + "deprecated": None} + actual = do_autodoc(app, 'module', 'target', options) + assert list(actual) == [ + '', + '.. py:module:: target', + ' :synopsis: Synopsis', + ' :platform: Platform', + ' :deprecated:', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_members(app): + # default (no-members) + actual = do_autodoc(app, 'class', 'target.inheritance.Base') + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ] + + # default ALL-members + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + # default specific-members + options = {"members": "inheritedmeth,inheritedstaticmeth"} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + # ALL-members override autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + # members override autodoc_default_options + options = {"members": "inheritedmeth"} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedmeth()', + ] + + # members extends autodoc_default_options + options = {"members": "+inheritedmeth"} + app.config.autodoc_default_options["members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_exclude_members(app): + options = {"members": None, + "exclude-members": "inheritedmeth,inheritedstaticmeth"} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # members vs exclude-members + options = {"members": "inheritedmeth", + "exclude-members": "inheritedmeth"} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ] + + # + has no effect when autodoc_default_options are not present + options = {"members": None, + "exclude-members": "+inheritedmeth,inheritedstaticmeth"} + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # exclude-members overrides autodoc_default_options + options = {"members": None, + "exclude-members": "inheritedmeth"} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + # exclude-members extends autodoc_default_options + options = {"members": None, + "exclude-members": "+inheritedmeth"} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # no exclude-members causes use autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ] + + # empty exclude-members cancels autodoc_default_options + options = {"members": None, + "exclude-members": None} + app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth" + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', + ' .. py:method:: Base.inheritedclassmeth()', + ' .. py:method:: Base.inheritedmeth()', + ' .. py:method:: Base.inheritedstaticmeth(cls)', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_undoc_members(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:attribute:: Class.skipattr', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ' .. py:method:: Class.undocmeth()', + ] + + # use autodoc_default_options + options = {"members": None} + app.config.autodoc_default_options["undoc-members"] = None + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:attribute:: Class.skipattr', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ' .. py:method:: Class.undocmeth()', + ] + + # options negation work check + options = {"members": None, + "no-undoc-members": None} + app.config.autodoc_default_options["undoc-members"] = None + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_undoc_members_for_metadata_only(app): + # metadata only member is not displayed + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.metadata', options) + assert list(actual) == [ + '', + '.. py:module:: target.metadata', + '', + ] + + # metadata only member is displayed when undoc-member given + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.metadata', options) + assert list(actual) == [ + '', + '.. py:module:: target.metadata', + '', + '', + '.. py:function:: foo()', + ' :module: target.metadata', + '', + ' :meta metadata-only-docstring:', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_inherited_members(app): + options = {"members": None, + "inherited-members": None} + actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options) + assert list(filter(lambda l: 'method::' in l, actual)) == [ + ' .. py:method:: Derived.inheritedclassmeth()', + ' .. py:method:: Derived.inheritedmeth()', + ' .. py:method:: Derived.inheritedstaticmeth(cls)', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_inherited_members_Base(app): + options = {"members": None, + "inherited-members": "Base", + "special-members": None} + + # check methods for object class are shown + actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options) + assert ' .. py:method:: Derived.inheritedmeth()' in actual + assert ' .. py:method:: Derived.inheritedclassmeth' not in actual + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_inherited_members_None(app): + options = {"members": None, + "inherited-members": "None", + "special-members": None} + + # check methods for object class are shown + actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options) + assert ' .. py:method:: Derived.__init__()' in actual + assert ' .. py:method:: Derived.__str__()' in actual + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_imported_members(app): + options = {"members": None, + "imported-members": None, + "ignore-module-all": None} + actual = do_autodoc(app, 'module', 'target', options) + assert '.. py:function:: function_to_be_imported(app: ~sphinx.application.Sphinx | None) -> str' in actual + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_special_members(app): + # specific special methods + options = {"undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ] + + # combination with specific members + options = {"members": "attr,docattr", + "undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ] + + # all special methods + options = {"members": None, + "undoc-members": None, + "special-members": None} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.__annotations__', + ' .. py:attribute:: Class.__dict__', + ' .. py:method:: Class.__init__(arg)', + ' .. py:attribute:: Class.__module__', + ' .. py:method:: Class.__special1__()', + ' .. py:method:: Class.__special2__()', + ' .. py:attribute:: Class.__weakref__', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:attribute:: Class.skipattr', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ' .. py:method:: Class.undocmeth()', + ] + + # specific special methods from autodoc_default_options + options = {"undoc-members": None} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__special2__()', + ] + + # specific special methods option with autodoc_default_options + options = {"undoc-members": None, + "special-members": "__init__,__special1__"} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ] + + # specific special methods merge with autodoc_default_options + options = {"undoc-members": None, + "special-members": "+__init__,__special1__"} + app.config.autodoc_default_options["special-members"] = "__special2__" + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ' .. py:method:: Class.__special2__()', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_ignore_module_all(app): + # default (no-ignore-module-all) + options = {"members": None} + actual = do_autodoc(app, 'module', 'target', options) + assert list(filter(lambda l: 'class::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ] + + # ignore-module-all + options = {"members": None, + "ignore-module-all": None} + actual = do_autodoc(app, 'module', 'target', options) + assert list(filter(lambda l: 'class::' in l, actual)) == [ + '.. py:class:: Class(arg)', + '.. py:class:: CustomDict', + '.. py:class:: InnerChild()', + '.. py:class:: InstAttCls()', + '.. py:class:: Outer()', + ' .. py:class:: Outer.Inner()', + '.. py:class:: StrRepr', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_noindex(app): + options = {"no-index": None} + actual = do_autodoc(app, 'module', 'target', options) + assert list(actual) == [ + '', + '.. py:module:: target', + ' :no-index:', + '', + ] + + # TODO: :no-index: should be propagated to children of target item. + + actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) + assert list(actual) == [ + '', + '.. py:class:: Base()', + ' :no-index:', + ' :module: target.inheritance', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_subclass_of_builtin_class(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.CustomDict', options) + assert list(actual) == [ + '', + '.. py:class:: CustomDict', + ' :module: target', + '', + ' Docstring.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_inner_class(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.Outer', options) + assert list(actual) == [ + '', + '.. py:class:: Outer()', + ' :module: target', + '', + ' Foo', + '', + '', + ' .. py:class:: Outer.Inner()', + ' :module: target', + '', + ' Foo', + '', + '', + ' .. py:method:: Outer.Inner.meth()', + ' :module: target', + '', + ' Foo', + '', + '', + ' .. py:attribute:: Outer.factory', + ' :module: target', + '', + ' alias of :py:class:`dict`', + ] + + actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) + assert list(actual) == [ + '', + '.. py:class:: Inner()', + ' :module: target.Outer', + '', + ' Foo', + '', + '', + ' .. py:method:: Inner.meth()', + ' :module: target.Outer', + '', + ' Foo', + '', + ] + + options['show-inheritance'] = None + actual = do_autodoc(app, 'class', 'target.InnerChild', options) + assert list(actual) == [ + '', + '.. py:class:: InnerChild()', + ' :module: target', '', + ' Bases: :py:class:`~target.Outer.Inner`', + '', + ' InnerChild docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_classmethod(app): + actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedclassmeth') + assert list(actual) == [ + '', + '.. py:method:: Base.inheritedclassmeth()', + ' :module: target.inheritance', + ' :classmethod:', + '', + ' Inherited class method.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_staticmethod(app): + actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedstaticmeth') + assert list(actual) == [ + '', + '.. py:method:: Base.inheritedstaticmeth(cls)', + ' :module: target.inheritance', + ' :staticmethod:', + '', + ' Inherited static method.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_descriptor(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'class', 'target.descriptor.Class', options) + assert list(actual) == [ + '', + '.. py:class:: Class()', + ' :module: target.descriptor', + '', + '', + ' .. py:attribute:: Class.descr', + ' :module: target.descriptor', + '', + ' Descriptor instance docstring.', + '', + '', + ' .. py:property:: Class.prop', + ' :module: target.descriptor', + '', + ' Property.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_cached_property(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options) + assert list(actual) == [ + '', + '.. py:class:: Foo()', + ' :module: target.cached_property', + '', + '', + ' .. py:property:: Foo.prop', + ' :module: target.cached_property', + ' :type: int', + '', + '', + ' .. py:property:: Foo.prop_with_type_comment', + ' :module: target.cached_property', + ' :type: int', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_member_order(app): + # case member-order='bysource' + options = {"members": None, + 'member-order': 'bysource', + "undoc-members": None, + 'private-members': None} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.undocmeth()', + ' .. py:method:: Class.skipmeth()', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.skipattr', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:attribute:: Class.udocattr', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class._private_inst_attr', + ] + + # case member-order='groupwise' + options = {"members": None, + 'member-order': 'groupwise', + "undoc-members": None, + 'private-members': None} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.excludemeth()', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:method:: Class.skipmeth()', + ' .. py:method:: Class.undocmeth()', + ' .. py:attribute:: Class._private_inst_attr', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:attribute:: Class.skipattr', + ' .. py:attribute:: Class.udocattr', + ] + + # case member-order=None + options = {"members": None, + "undoc-members": None, + 'private-members': None} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:attribute:: Class._private_inst_attr', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ' .. py:method:: Class.excludemeth()', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_inline', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:attribute:: Class.mdocattr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.moore(a, e, f) -> happiness', + ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + ' .. py:attribute:: Class.skipattr', + ' .. py:method:: Class.skipmeth()', + ' .. py:attribute:: Class.udocattr', + ' .. py:method:: Class.undocmeth()', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_module_member_order(app): + # case member-order='bysource' + options = {"members": 'foo, Bar, baz, qux, Quux, foobar', + 'member-order': 'bysource', + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.sort_by_all', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:module:: target.sort_by_all', + '.. py:function:: baz()', + '.. py:function:: foo()', + '.. py:class:: Bar()', + '.. py:class:: Quux()', + '.. py:function:: foobar()', + '.. py:function:: qux()', + ] + + # case member-order='bysource' and ignore-module-all + options = {"members": 'foo, Bar, baz, qux, Quux, foobar', + 'member-order': 'bysource', + "undoc-members": None, + "ignore-module-all": None} + actual = do_autodoc(app, 'module', 'target.sort_by_all', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:module:: target.sort_by_all', + '.. py:function:: foo()', + '.. py:class:: Bar()', + '.. py:function:: baz()', + '.. py:function:: qux()', + '.. py:class:: Quux()', + '.. py:function:: foobar()', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_module_scope(app): + app.env.temp_data['autodoc:module'] = 'target' + actual = do_autodoc(app, 'attribute', 'Class.mdocattr') + assert list(actual) == [ + '', + '.. py:attribute:: Class.mdocattr', + ' :module: target', + ' :value: <_io.StringIO object>', + '', + ' should be documented as well - süß', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_class_scope(app): + app.env.temp_data['autodoc:module'] = 'target' + app.env.temp_data['autodoc:class'] = 'Class' + actual = do_autodoc(app, 'attribute', 'mdocattr') + assert list(actual) == [ + '', + '.. py:attribute:: Class.mdocattr', + ' :module: target', + ' :value: <_io.StringIO object>', + '', + ' should be documented as well - süß', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_class_attributes(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'class', 'target.AttCls', options) + assert list(actual) == [ + '', + '.. py:class:: AttCls()', + ' :module: target', + '', + '', + ' .. py:attribute:: AttCls.a1', + ' :module: target', + ' :value: hello world', + '', + '', + ' .. py:attribute:: AttCls.a2', + ' :module: target', + ' :value: None', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoclass_instance_attributes(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.InstAttCls', options) + assert list(actual) == [ + '', + '.. py:class:: InstAttCls()', + ' :module: target', + '', + ' Class with documented class and instance attributes.', + '', + '', + ' .. py:attribute:: InstAttCls.ca1', + ' :module: target', + " :value: 'a'", + '', + ' Doc comment for class attribute InstAttCls.ca1.', + ' It can have multiple lines.', + '', + '', + ' .. py:attribute:: InstAttCls.ca2', + ' :module: target', + " :value: 'b'", + '', + ' Doc comment for InstAttCls.ca2. One line only.', + '', + '', + ' .. py:attribute:: InstAttCls.ca3', + ' :module: target', + " :value: 'c'", + '', + ' Docstring for class attribute InstAttCls.ca3.', + '', + '', + ' .. py:attribute:: InstAttCls.ia1', + ' :module: target', + '', + ' Doc comment for instance attribute InstAttCls.ia1', + '', + '', + ' .. py:attribute:: InstAttCls.ia2', + ' :module: target', + '', + ' Docstring for instance attribute InstAttCls.ia2.', + '', + ] + + # pick up arbitrary attributes + options = {"members": 'ca1,ia1'} + actual = do_autodoc(app, 'class', 'target.InstAttCls', options) + assert list(actual) == [ + '', + '.. py:class:: InstAttCls()', + ' :module: target', + '', + ' Class with documented class and instance attributes.', + '', + '', + ' .. py:attribute:: InstAttCls.ca1', + ' :module: target', + " :value: 'a'", + '', + ' Doc comment for class attribute InstAttCls.ca1.', + ' It can have multiple lines.', + '', + '', + ' .. py:attribute:: InstAttCls.ia1', + ' :module: target', + '', + ' Doc comment for instance attribute InstAttCls.ia1', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_instance_attributes(app): + actual = do_autodoc(app, 'attribute', 'target.InstAttCls.ia1') + assert list(actual) == [ + '', + '.. py:attribute:: InstAttCls.ia1', + ' :module: target', + '', + ' Doc comment for instance attribute InstAttCls.ia1', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_slots(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.slots', options) + assert list(actual) == [ + '', + '.. py:module:: target.slots', + '', + '', + '.. py:class:: Bar()', + ' :module: target.slots', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Bar.attr1', + ' :module: target.slots', + ' :type: int', + '', + ' docstring of attr1', + '', + '', + ' .. py:attribute:: Bar.attr2', + ' :module: target.slots', + '', + ' docstring of instance attr2', + '', + '', + ' .. py:attribute:: Bar.attr3', + ' :module: target.slots', + '', + '', + '.. py:class:: Baz()', + ' :module: target.slots', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Baz.attr', + ' :module: target.slots', + '', + '', + '.. py:class:: Foo()', + ' :module: target.slots', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.attr', + ' :module: target.slots', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_enum_class(app): + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options) + + if sys.version_info[:2] >= (3, 12): + args = ('(value, names=None, *values, module=None, ' + 'qualname=None, type=None, start=1, boundary=None)') + elif sys.version_info[:2] >= (3, 11): + args = ('(value, names=None, *, module=None, qualname=None, ' + 'type=None, start=1, boundary=None)') + else: + args = '(value)' + + assert list(actual) == [ + '', + '.. py:class:: EnumCls' + args, + ' :module: target.enums', + '', + ' this is enum class', + '', + '', + ' .. py:method:: EnumCls.say_goodbye()', + ' :module: target.enums', + ' :classmethod:', + '', + ' a classmethod says good-bye to you.', + '', + '', + ' .. py:method:: EnumCls.say_hello()', + ' :module: target.enums', + '', + ' a method says hello to you.', + '', + '', + ' .. py:attribute:: EnumCls.val1', + ' :module: target.enums', + ' :value: 12', + '', + ' doc for val1', + '', + '', + ' .. py:attribute:: EnumCls.val2', + ' :module: target.enums', + ' :value: 23', + '', + ' doc for val2', + '', + '', + ' .. py:attribute:: EnumCls.val3', + ' :module: target.enums', + ' :value: 34', + '', + ' doc for val3', + '', + ] + + # checks for an attribute of EnumClass + actual = do_autodoc(app, 'attribute', 'target.enums.EnumCls.val1') + assert list(actual) == [ + '', + '.. py:attribute:: EnumCls.val1', + ' :module: target.enums', + ' :value: 12', + '', + ' doc for val1', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_descriptor_class(app): + options = {"members": 'CustomDataDescriptor,CustomDataDescriptor2'} + actual = do_autodoc(app, 'module', 'target.descriptor', options) + assert list(actual) == [ + '', + '.. py:module:: target.descriptor', + '', + '', + '.. py:class:: CustomDataDescriptor(doc)', + ' :module: target.descriptor', + '', + ' Descriptor class docstring.', + '', + '', + ' .. py:method:: CustomDataDescriptor.meth()', + ' :module: target.descriptor', + '', + ' Function.', + '', + '', + '.. py:class:: CustomDataDescriptor2(doc)', + ' :module: target.descriptor', + '', + ' Descriptor class with custom metaclass docstring.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_automethod_for_builtin(app): + actual = do_autodoc(app, 'method', 'builtins.int.__add__') + assert list(actual) == [ + '', + '.. py:method:: int.__add__(value, /)', + ' :module: builtins', + '', + ' Return self+value.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_automethod_for_decorated(app): + actual = do_autodoc(app, 'method', 'target.decorator.Bar.meth') + assert list(actual) == [ + '', + '.. py:method:: Bar.meth(name=None, age=None)', + ' :module: target.decorator', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_abstractmethods(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.abstractmethods', options) + assert list(actual) == [ + '', + '.. py:module:: target.abstractmethods', + '', + '', + '.. py:class:: Base()', + ' :module: target.abstractmethods', + '', + '', + ' .. py:method:: Base.abstractmeth()', + ' :module: target.abstractmethods', + ' :abstractmethod:', + '', + '', + ' .. py:method:: Base.classmeth()', + ' :module: target.abstractmethods', + ' :abstractmethod:', + ' :classmethod:', + '', + '', + ' .. py:method:: Base.coroutinemeth()', + ' :module: target.abstractmethods', + ' :abstractmethod:', + ' :async:', + '', + '', + ' .. py:method:: Base.meth()', + ' :module: target.abstractmethods', + '', + '', + ' .. py:property:: Base.prop', + ' :module: target.abstractmethods', + ' :abstractmethod:', + '', + '', + ' .. py:method:: Base.staticmeth()', + ' :module: target.abstractmethods', + ' :abstractmethod:', + ' :staticmethod:', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_partialfunction(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.partialfunction', options) + assert list(actual) == [ + '', + '.. py:module:: target.partialfunction', + '', + '', + '.. py:function:: func1(a, b, c)', + ' :module: target.partialfunction', + '', + ' docstring of func1', + '', + '', + '.. py:function:: func2(b, c)', + ' :module: target.partialfunction', + '', + ' docstring of func1', + '', + '', + '.. py:function:: func3(c)', + ' :module: target.partialfunction', + '', + ' docstring of func3', + '', + '', + '.. py:function:: func4()', + ' :module: target.partialfunction', + '', + ' docstring of func3', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_imported_partialfunction_should_not_shown_without_imported_members(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.imported_members', options) + assert list(actual) == [ + '', + '.. py:module:: target.imported_members', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_bound_method(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.bound_method', options) + assert list(actual) == [ + '', + '.. py:module:: target.bound_method', + '', + '', + '.. py:function:: bound_method()', + ' :module: target.bound_method', + '', + ' Method docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_partialmethod(app): + expected = [ + '', + '.. py:class:: Cell()', + ' :module: target.partialmethod', + '', + ' An example for partialmethod.', + '', + ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', + '', + '', + ' .. py:method:: Cell.set_alive()', + ' :module: target.partialmethod', + '', + ' Make a cell alive.', + '', + '', + ' .. py:method:: Cell.set_state(state)', + ' :module: target.partialmethod', + '', + ' Update state of cell to *state*.', + '', + ] + + options = {"members": None} + actual = do_autodoc(app, 'class', 'target.partialmethod.Cell', options) + assert list(actual) == expected + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_partialmethod_undoc_members(app): + expected = [ + '', + '.. py:class:: Cell()', + ' :module: target.partialmethod', + '', + ' An example for partialmethod.', + '', + ' refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod', + '', + '', + ' .. py:method:: Cell.set_alive()', + ' :module: target.partialmethod', + '', + ' Make a cell alive.', + '', + '', + ' .. py:method:: Cell.set_dead()', + ' :module: target.partialmethod', + '', + '', + ' .. py:method:: Cell.set_state(state)', + ' :module: target.partialmethod', + '', + ' Update state of cell to *state*.', + '', + ] + + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'class', 'target.partialmethod.Cell', options) + assert list(actual) == expected + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_typed_instance_variables(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.typed_vars', options) + assert list(actual) == [ + '', + '.. py:module:: target.typed_vars', + '', + '', + '.. py:attribute:: Alias', + ' :module: target.typed_vars', + '', + ' alias of :py:class:`~target.typed_vars.Derived`', + '', + '.. py:class:: Class()', + ' :module: target.typed_vars', + '', + '', + ' .. py:attribute:: Class.attr1', + ' :module: target.typed_vars', + ' :type: int', + ' :value: 0', + '', + '', + ' .. py:attribute:: Class.attr2', + ' :module: target.typed_vars', + ' :type: int', + '', + '', + ' .. py:attribute:: Class.attr3', + ' :module: target.typed_vars', + ' :type: int', + ' :value: 0', + '', + '', + ' .. py:attribute:: Class.attr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr4', + '', + '', + ' .. py:attribute:: Class.attr5', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr5', + '', + '', + ' .. py:attribute:: Class.attr6', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr6', + '', + '', + ' .. py:attribute:: Class.descr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' This is descr4', + '', + '', + '.. py:class:: Derived()', + ' :module: target.typed_vars', + '', + '', + ' .. py:attribute:: Derived.attr7', + ' :module: target.typed_vars', + ' :type: int', + '', + '', + '.. py:data:: attr1', + ' :module: target.typed_vars', + ' :type: str', + " :value: ''", + '', + ' attr1', + '', + '', + '.. py:data:: attr2', + ' :module: target.typed_vars', + ' :type: str', + '', + ' attr2', + '', + '', + '.. py:data:: attr3', + ' :module: target.typed_vars', + ' :type: str', + " :value: ''", + '', + ' attr3', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_typed_inherited_instance_variables(app): + options = {"members": None, + "undoc-members": None, + "inherited-members": None} + actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options) + assert list(actual) == [ + '', + '.. py:class:: Derived()', + ' :module: target.typed_vars', + '', + '', + ' .. py:attribute:: Derived.attr1', + ' :module: target.typed_vars', + ' :type: int', + ' :value: 0', + '', + '', + ' .. py:attribute:: Derived.attr2', + ' :module: target.typed_vars', + ' :type: int', + '', + '', + ' .. py:attribute:: Derived.attr3', + ' :module: target.typed_vars', + ' :type: int', + ' :value: 0', + '', + '', + ' .. py:attribute:: Derived.attr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr4', + '', + '', + ' .. py:attribute:: Derived.attr5', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr5', + '', + '', + ' .. py:attribute:: Derived.attr6', + ' :module: target.typed_vars', + ' :type: int', + '', + ' attr6', + '', + '', + ' .. py:attribute:: Derived.attr7', + ' :module: target.typed_vars', + ' :type: int', + '', + '', + ' .. py:attribute:: Derived.descr4', + ' :module: target.typed_vars', + ' :type: int', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_GenericAlias(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.genericalias', options) + assert list(actual) == [ + '', + '.. py:module:: target.genericalias', + '', + '', + '.. py:class:: Class()', + ' :module: target.genericalias', + '', + '', + ' .. py:attribute:: Class.T', + ' :module: target.genericalias', + '', + ' A list of int', + '', + ' alias of :py:class:`~typing.List`\\ [:py:class:`int`]', + '', + '', + '.. py:data:: L', + ' :module: target.genericalias', + '', + ' A list of Class', + '', + ' alias of :py:class:`~typing.List`\\ ' + '[:py:class:`~target.genericalias.Class`]', + '', + '', + '.. py:data:: T', + ' :module: target.genericalias', + '', + ' A list of int', + '', + ' alias of :py:class:`~typing.List`\\ [:py:class:`int`]', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_TypeVar(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.typevar', options) + assert list(actual) == [ + '', + '.. py:module:: target.typevar', + '', + '', + '.. py:class:: Class()', + ' :module: target.typevar', + '', + '', + ' .. py:class:: Class.T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + '', + ' .. py:class:: Class.T6', + ' :module: target.typevar', + '', + ' T6', + '', + ' alias of :py:class:`~datetime.date`', + '', + '', + '.. py:class:: T1', + ' :module: target.typevar', + '', + ' T1', + '', + " alias of TypeVar('T1')", + '', + '', + '.. py:class:: T3', + ' :module: target.typevar', + '', + ' T3', + '', + " alias of TypeVar('T3', int, str)", + '', + '', + '.. py:class:: T4', + ' :module: target.typevar', + '', + ' T4', + '', + " alias of TypeVar('T4', covariant=True)", + '', + '', + '.. py:class:: T5', + ' :module: target.typevar', + '', + ' T5', + '', + " alias of TypeVar('T5', contravariant=True)", + '', + '', + '.. py:class:: T6', + ' :module: target.typevar', + '', + ' T6', + '', + ' alias of :py:class:`~datetime.date`', + '', + '', + '.. py:class:: T7', + ' :module: target.typevar', + '', + ' T7', + '', + " alias of TypeVar('T7', bound=\\ :py:class:`int`)", + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_Annotated(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.annotated', options) + assert list(actual) == [ + '', + '.. py:module:: target.annotated', + '', + '', + '.. py:function:: hello(name: str) -> None', + ' :module: target.annotated', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_TYPE_CHECKING(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.TYPE_CHECKING', options) + assert list(actual) == [ + '', + '.. py:module:: target.TYPE_CHECKING', + '', + '', + '.. py:class:: Foo()', + ' :module: target.TYPE_CHECKING', + '', + '', + ' .. py:attribute:: Foo.attr1', + ' :module: target.TYPE_CHECKING', + ' :type: ~_io.StringIO', + '', + '', + '.. py:function:: spam(ham: ~collections.abc.Iterable[str]) -> tuple[~gettext.NullTranslations, bool]', + ' :module: target.TYPE_CHECKING', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_TYPE_CHECKING_circular_import(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'circular_import', options) + assert list(actual) == [ + '', + '.. py:module:: circular_import', + '', + ] + assert sys.modules["circular_import"].a is sys.modules["circular_import.a"] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_singledispatch(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatch', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatch', + '', + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: float, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' func(arg: dict, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_singledispatchmethod(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatchmethod', + '', + '', + '.. py:class:: Foo()', + ' :module: target.singledispatchmethod', + '', + ' docstring', + '', + '', + ' .. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: float, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' Foo.meth(arg: dict, kwarg=None)', + ' :module: target.singledispatchmethod', + '', + ' A method for general use.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_singledispatchmethod_automethod(app): + options = {} + actual = do_autodoc(app, 'method', 'target.singledispatchmethod.Foo.meth', options) + assert list(actual) == [ + '', + '.. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: float, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' Foo.meth(arg: dict, kwarg=None)', + ' :module: target.singledispatchmethod', + '', + ' A method for general use.', + '', + ] + + +@pytest.mark.skipif(sys.version_info[:2] >= (3, 13), + reason='Cython does not support Python 3.13 yet.') +@pytest.mark.skipif(pyximport is None, reason='cython is not installed') +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_cython(app): + options = {"members": None, + "undoc-members": None} + actual = do_autodoc(app, 'module', 'target.cython', options) + assert list(actual) == [ + '', + '.. py:module:: target.cython', + '', + '', + '.. py:class:: Class()', + ' :module: target.cython', + '', + ' Docstring.', + '', + '', + ' .. py:method:: Class.meth(name: str, age: int = 0) -> None', + ' :module: target.cython', + '', + ' Docstring.', + '', + '', + '.. py:function:: foo(x: int, *args, y: str, **kwargs)', + ' :module: target.cython', + '', + ' Docstring.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_final(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.final', options) + assert list(actual) == [ + '', + '.. py:module:: target.final', + '', + '', + '.. py:class:: Class()', + ' :module: target.final', + ' :final:', + '', + ' docstring', + '', + '', + ' .. py:method:: Class.meth1()', + ' :module: target.final', + ' :final:', + '', + ' docstring', + '', + '', + ' .. py:method:: Class.meth2()', + ' :module: target.final', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_overload(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.overload', options) + assert list(actual) == [ + '', + '.. py:module:: target.overload', + '', + '', + '.. py:class:: Bar(x: int, y: int)', + ' Bar(x: str, y: str)', + ' :module: target.overload', + '', + ' docstring', + '', + '', + '.. py:class:: Baz(x: int, y: int)', + ' Baz(x: str, y: str)', + ' :module: target.overload', + '', + ' docstring', + '', + '', + '.. py:class:: Foo(x: int, y: int)', + ' Foo(x: str, y: str)', + ' :module: target.overload', + '', + ' docstring', + '', + '', + '.. py:class:: Math()', + ' :module: target.overload', + '', + ' docstring', + '', + '', + ' .. py:method:: Math.sum(x: int, y: int = 0) -> int', + ' Math.sum(x: float, y: float = 0.0) -> float', + ' Math.sum(x: str, y: str = None) -> str', + ' :module: target.overload', + '', + ' docstring', + '', + '', + '.. py:function:: sum(x: int, y: int = 0) -> int', + ' sum(x: float, y: float = 0.0) -> float', + ' sum(x: str, y: str = None) -> str', + ' :module: target.overload', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_overload2(app): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.overload2', options) + assert list(actual) == [ + '', + '.. py:module:: target.overload2', + '', + '', + '.. py:class:: Baz(x: int, y: int)', + ' Baz(x: str, y: str)', + ' :module: target.overload2', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pymodule_for_ModuleLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target.classes' + actual = do_autodoc(app, 'class', 'Foo') + assert list(actual) == [ + '', + '.. py:class:: Foo()', + ' :module: target.classes', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pymodule_for_ClassLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target.methods' + actual = do_autodoc(app, 'method', 'Base.meth') + assert list(actual) == [ + '', + '.. py:method:: Base.meth()', + ' :module: target.methods', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pyclass_for_ClassLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target.methods' + app.env.ref_context['py:class'] = 'Base' + actual = do_autodoc(app, 'method', 'meth') + assert list(actual) == [ + '', + '.. py:method:: Base.meth()', + ' :module: target.methods', + '', + ] + + +@pytest.mark.sphinx('dummy', testroot='ext-autodoc') +def test_autodoc(app, status, warning): + app.builder.build_all() + + content = app.env.get_doctree('index') + assert isinstance(content[3], addnodes.desc) + assert content[3][0].astext() == 'autodoc_dummy_module.test()' + assert content[3][1].astext() == 'Dummy function using dummy.*' + + # issue sphinx-doc/sphinx#2437 + assert content[11][-1].astext() == """Dummy class Bar with alias. + + + +my_name + +alias of Foo""" + assert warning.getvalue() == '' + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_name_conflict(app): + actual = do_autodoc(app, 'class', 'target.name_conflict.foo') + assert list(actual) == [ + '', + '.. py:class:: foo()', + ' :module: target.name_conflict', + '', + ' docstring of target.name_conflict::foo.', + '', + ] + + actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar') + assert list(actual) == [ + '', + '.. py:class:: bar()', + ' :module: target.name_conflict.foo', + '', + ' docstring of target.name_conflict.foo::bar.', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_name_mangling(app): + options = {"members": None, + "undoc-members": None, + "private-members": None} + actual = do_autodoc(app, 'module', 'target.name_mangling', options) + assert list(actual) == [ + '', + '.. py:module:: target.name_mangling', + '', + '', + '.. py:class:: Bar()', + ' :module: target.name_mangling', + '', + '', + ' .. py:attribute:: Bar._Baz__email', + ' :module: target.name_mangling', + ' :value: None', + '', + ' a member having mangled-like name', + '', + '', + ' .. py:attribute:: Bar.__address', + ' :module: target.name_mangling', + ' :value: None', + '', + '', + '.. py:class:: Foo()', + ' :module: target.name_mangling', + '', + '', + ' .. py:attribute:: Foo.__age', + ' :module: target.name_mangling', + ' :value: None', + '', + '', + ' .. py:attribute:: Foo.__name', + ' :module: target.name_mangling', + ' :value: None', + '', + ' name of Foo', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_type_union_operator(app): + options = {'members': None} + actual = do_autodoc(app, 'module', 'target.pep604', options) + assert list(actual) == [ + '', + '.. py:module:: target.pep604', + '', + '', + '.. py:class:: Foo()', + ' :module: target.pep604', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.attr', + ' :module: target.pep604', + ' :type: int | str', + '', + ' docstring', + '', + '', + ' .. py:method:: Foo.meth(x: int | str, y: int | str) -> int | str', + ' :module: target.pep604', + '', + ' docstring', + '', + '', + '.. py:data:: attr', + ' :module: target.pep604', + ' :type: int | str', + '', + ' docstring', + '', + '', + '.. py:function:: sum(x: int | str, y: int | str) -> int | str', + ' :module: target.pep604', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_hide_value(app): + options = {'members': None} + actual = do_autodoc(app, 'module', 'target.hide_value', options) + assert list(actual) == [ + '', + '.. py:module:: target.hide_value', + '', + '', + '.. py:class:: Foo()', + ' :module: target.hide_value', + '', + ' docstring', + '', + '', + ' .. py:attribute:: Foo.SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + '', + ' .. py:attribute:: Foo.SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + '', + '.. py:data:: SENTINEL1', + ' :module: target.hide_value', + '', + ' docstring', + '', + ' :meta hide-value:', + '', + '', + '.. py:data:: SENTINEL2', + ' :module: target.hide_value', + '', + ' :meta hide-value:', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_canonical(app): + options = {'members': None, + 'imported-members': None} + actual = do_autodoc(app, 'module', 'target.canonical', options) + assert list(actual) == [ + '', + '.. py:module:: target.canonical', + '', + '', + '.. py:class:: Bar()', + ' :module: target.canonical', + '', + ' docstring', + '', + '', + '.. py:class:: Foo()', + ' :module: target.canonical', + ' :canonical: target.canonical.original.Foo', + '', + ' docstring', + '', + '', + ' .. py:method:: Foo.meth()', + ' :module: target.canonical', + '', + ' docstring', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_literal_render(app): + def bounded_typevar_rst(name, bound): + return [ + '', + f'.. py:class:: {name}', + ' :module: target.literal', + '', + ' docstring', + '', + f' alias of TypeVar({name!r}, bound={bound})', + '', + ] + + def function_rst(name, sig): + return [ + '', + f'.. py:function:: {name}({sig})', + ' :module: target.literal', + '', + ' docstring', + '', + ] + + # autodoc_typehints_format can take 'short' or 'fully-qualified' values + # and this will be interpreted as 'smart' or 'fully-qualified-except-typing' by restify() + # and 'smart' or 'fully-qualified' by stringify_annotation(). + + options = {'members': None, 'exclude-members': 'MyEnum'} + app.config.autodoc_typehints_format = 'short' + actual = do_autodoc(app, 'module', 'target.literal', options) + assert list(actual) == [ + '', + '.. py:module:: target.literal', + '', + *bounded_typevar_rst('T', r'\ :py:obj:`~typing.Literal`\ [1234]'), + *bounded_typevar_rst('U', r'\ :py:obj:`~typing.Literal`\ [:py:attr:`~target.literal.MyEnum.a`]'), + *function_rst('bar', 'x: ~typing.Literal[1234]'), + *function_rst('foo', 'x: ~typing.Literal[MyEnum.a]'), + ] + + # restify() assumes that 'fully-qualified' is 'fully-qualified-except-typing' + # because it is more likely that a user wants to suppress 'typing.*' + app.config.autodoc_typehints_format = 'fully-qualified' + actual = do_autodoc(app, 'module', 'target.literal', options) + assert list(actual) == [ + '', + '.. py:module:: target.literal', + '', + *bounded_typevar_rst('T', r'\ :py:obj:`~typing.Literal`\ [1234]'), + *bounded_typevar_rst('U', r'\ :py:obj:`~typing.Literal`\ [:py:attr:`target.literal.MyEnum.a`]'), + *function_rst('bar', 'x: typing.Literal[1234]'), + *function_rst('foo', 'x: typing.Literal[target.literal.MyEnum.a]'), + ] |