"""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]'), ]