sphinx/tests/test_autodoc.py
Lewis Haley f196a92055 autodoc: add 'exclude-members' to user global options
As the previous commit explains, it is now possible to specify arguments
to the global options in config files. This means that we can now include
the `exclude-members` option in this global configuration.

Previously, there was no point including this option because it makes no
sense without arguments. Including this option means users have the
flexibility of explicitly including which special methods they want using
(e.g.):

  :special-members: __init__, __iter__

or explicitly excluding which special-members (or other members) they want
using (e.g.):

  :exclude-members: __weakref__, __hash__
2018-08-17 16:02:11 +01:00

1564 lines
52 KiB
Python

# -*- coding: utf-8 -*-
"""
test_autodoc
~~~~~~~~~~~~
Test the autodoc extension. This tests mainly the Documenters; the auto
directives are tested in a test source file translated by test_build.
:copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import platform
import sys
from warnings import catch_warnings
import pytest
from docutils.statemachine import ViewList
from six import PY3
from sphinx.ext.autodoc import (
AutoDirective, ModuleLevelDocumenter, cut_lines, between, ALL,
convert_autodoc_default_flags
)
from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options
from sphinx.testing.util import SphinxTestApp, Struct # NOQA
from sphinx.util import logging
from sphinx.util.docutils import LoggingReporter
app = None
if PY3:
ROGER_METHOD = ' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)'
else:
ROGER_METHOD = ' .. py:classmethod:: Class.roger(a, e=5, f=6)'
IS_PYPY = platform.python_implementation() == 'PyPy'
def do_autodoc(app, objtype, name, options=None):
if options is None:
options = {}
doccls = app.registry.documenters[objtype]
docoptions = process_documenter_options(doccls, app.config, options)
bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1)
documenter = doccls(bridge, name)
documenter.generate()
return bridge.result
@pytest.fixture(scope='module', autouse=True)
def setup_module(rootdir, sphinx_test_tempdir):
try:
global app
srcdir = sphinx_test_tempdir / 'autodoc-root'
if not srcdir.exists():
(rootdir / 'test-root').copytree(srcdir)
testroot = rootdir / 'test-ext-autodoc'
sys.path.append(testroot)
app = SphinxTestApp(srcdir=srcdir)
app.builder.env.app = app
app.builder.env.temp_data['docname'] = 'dummy'
app.connect('autodoc-process-docstring', process_docstring)
app.connect('autodoc-process-signature', process_signature)
app.connect('autodoc-skip-member', skip_member)
yield
finally:
app.cleanup()
sys.path.remove(testroot)
directive = options = None
@pytest.fixture
def setup_test():
global options, directive
global processed_docstrings, processed_signatures
options = Struct(
inherited_members = False,
undoc_members = False,
private_members = False,
special_members = False,
imported_members = False,
show_inheritance = False,
noindex = False,
annotation = None,
synopsis = '',
platform = '',
deprecated = False,
members = [],
member_order = 'alphabetic',
exclude_members = set(),
ignore_module_all = False,
)
directive = Struct(
env = app.builder.env,
genopt = options,
result = ViewList(),
filename_set = set(),
)
processed_docstrings = []
processed_signatures = []
app._status.truncate(0)
app._warning.truncate(0)
yield
AutoDirective._special_attrgetters.clear()
processed_docstrings = []
processed_signatures = []
def process_docstring(app, what, name, obj, options, lines):
processed_docstrings.append((what, name))
if name == 'bar':
lines.extend(['42', ''])
def process_signature(app, what, name, obj, options, args, retann):
processed_signatures.append((what, name))
if name == 'bar':
return '42', 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
@pytest.mark.usefixtures('setup_test')
def test_parse_name():
logging.setup(app, app._status, app._warning)
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
# for modules
verify('module', 'test_autodoc', ('test_autodoc', [], None, None))
verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None))
verify('module', 'test(arg)', ('test', [], 'arg', None))
assert 'signature arguments' in app._warning.getvalue()
# for functions/classes
verify('function', 'test_autodoc.raises',
('test_autodoc', ['raises'], None, None))
verify('function', 'test_autodoc.raises(exc) -> None',
('test_autodoc', ['raises'], 'exc', 'None'))
directive.env.temp_data['autodoc:module'] = 'test_autodoc'
verify('function', 'raises', ('test_autodoc', ['raises'], None, None))
del directive.env.temp_data['autodoc:module']
directive.env.ref_context['py:module'] = 'test_autodoc'
verify('function', 'raises', ('test_autodoc', ['raises'], None, None))
verify('class', 'Base', ('test_autodoc', ['Base'], None, None))
# for members
directive.env.ref_context['py:module'] = 'foo'
verify('method', 'util.SphinxTestApp.cleanup',
('util', ['SphinxTestApp', 'cleanup'], None, None))
directive.env.ref_context['py:module'] = 'util'
directive.env.ref_context['py:class'] = 'Foo'
directive.env.temp_data['autodoc:class'] = 'SphinxTestApp'
verify('method', 'cleanup', ('util', ['SphinxTestApp', 'cleanup'], None, None))
verify('method', 'SphinxTestApp.cleanup',
('util', ['SphinxTestApp', 'cleanup'], None, None))
# and clean up
del directive.env.ref_context['py:module']
del directive.env.ref_context['py:class']
del directive.env.temp_data['autodoc:class']
@pytest.mark.usefixtures('setup_test')
def test_format_signature():
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.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', 'f', f, None, 'None') == '(a, b, c=1, **d) -> None'
assert formatsig('function', 'g', g, None, None) == r"(a='\\n')"
# test for classes
class D:
pass
class E(object):
pass
# no signature for classes without __init__
for C in (D, E):
assert formatsig('class', 'D', C, None, None) == ''
class F:
def __init__(self, a, b=None):
pass
class G(F, object):
pass
for C in (F, G):
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
# __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, object):
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 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.usefixtures('setup_test')
def test_get_doc():
def getdocl(objtype, obj, encoding=None):
inst = app.registry.documenters[objtype](directive, 'tmp')
inst.object = obj
inst.objpath = [obj.__name__]
inst.doc_as_attr = False
inst.format_signature() # handle docstring signatures!
ds = inst.get_doc(encoding)
# 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) == [u'Döcstring']
# already-unicode docstrings must be taken literally
def f():
u"""Döcstring"""
assert getdocl('function', f) == [u'Döcstring']
# class docstring: depends on config value which one is taken
class C:
"""Class docstring"""
def __init__(self):
"""Init docstring"""
def __new__(cls):
"""New docstring"""
directive.env.config.autoclass_content = 'class'
assert getdocl('class', C) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', C) == ['Init docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', C) == ['Class docstring', '', 'Init docstring']
class D:
"""Class docstring"""
def __init__(self):
"""Init docstring
Other
lines
"""
# Indentation is normalized for 'both'
assert getdocl('class', D) == ['Class docstring', '', 'Init docstring',
'', 'Other', ' lines']
# __init__ have signature at first line of docstring
class E:
"""Class docstring"""
def __init__(self, *args, **kw):
"""
__init__(a1, a2, kw1=True, kw2=False)
Init docstring
"""
# signature line in the docstring will be kept when
# autodoc_docstring_signature == False
directive.env.config.autodoc_docstring_signature = False
directive.env.config.autoclass_content = 'class'
assert getdocl('class', E) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', E) == ['__init__(a1, a2, kw1=True, kw2=False)',
'', 'Init docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', E) == ['Class docstring', '',
'__init__(a1, a2, kw1=True, kw2=False)',
'', 'Init docstring']
# signature line in the docstring will be removed when
# autodoc_docstring_signature == True
directive.env.config.autodoc_docstring_signature = True # default
directive.env.config.autoclass_content = 'class'
assert getdocl('class', E) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', E) == ['Init docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', E) == ['Class docstring', '', 'Init docstring']
# class does not have __init__ method
class F(object):
"""Class docstring"""
# docstring in the __init__ method of base class will be discard
for f in (False, True):
directive.env.config.autodoc_docstring_signature = f
directive.env.config.autoclass_content = 'class'
assert getdocl('class', F) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', F) == ['Class docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', F) == ['Class docstring']
# class has __init__ method with no docstring
class G(object):
"""Class docstring"""
def __init__(self):
pass
# docstring in the __init__ method of base class will not be used
for f in (False, True):
directive.env.config.autodoc_docstring_signature = f
directive.env.config.autoclass_content = 'class'
assert getdocl('class', G) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', G) == ['Class docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', G) == ['Class docstring']
# class has __new__ method with docstring
# class docstring: depends on config value which one is taken
class H:
"""Class docstring"""
def __init__(self):
pass
def __new__(cls):
"""New docstring"""
directive.env.config.autoclass_content = 'class'
assert getdocl('class', H) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', H) == ['New docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', H) == ['Class docstring', '', 'New docstring']
# class has __init__ method without docstring and
# __new__ method with docstring
# class docstring: depends on config value which one is taken
class I: # NOQA
"""Class docstring"""
def __new__(cls):
"""New docstring"""
directive.env.config.autoclass_content = 'class'
assert getdocl('class', I) == ['Class docstring']
directive.env.config.autoclass_content = 'init'
assert getdocl('class', I) == ['New docstring']
directive.env.config.autoclass_content = 'both'
assert getdocl('class', I) == ['Class docstring', '', 'New docstring']
from target import Base, Derived
# NOTE: inspect.getdoc seems not to work with locally defined classes
directive.env.config.autodoc_inherit_docstrings = False
assert getdocl('method', Base.inheritedmeth) == ['Inherited function.']
assert getdocl('method', Derived.inheritedmeth) == []
directive.env.config.autodoc_inherit_docstrings = True
assert getdocl('method', Derived.inheritedmeth) == ['Inherited function.']
@pytest.mark.usefixtures('setup_test')
def test_docstring_processing():
def process(objtype, name, obj):
inst = app.registry.documenters[objtype](directive, name)
inst.object = obj
inst.fullname = name
return list(inst.process_doc(inst.get_doc()))
class E:
def __init__(self):
"""Init docstring"""
# docstring processing by event handler
assert process('class', 'bar', E) == ['Init docstring', '', '42', '']
lid = app.connect('autodoc-process-docstring',
cut_lines(1, 1, ['function']))
def f():
"""
first line
second line
third line
"""
assert process('function', 'f', f) == ['second line', '']
app.disconnect(lid)
lid = app.connect('autodoc-process-docstring', between('---', ['function']))
def g():
"""
first line
---
second line
---
third line
"""
assert process('function', 'g', g) == ['second line', '']
app.disconnect(lid)
lid = app.connect('autodoc-process-docstring',
between('---', ['function'], exclude=True))
def h():
"""
first line
---
second line
---
third line
"""
assert process('function', 'h', h) == ['first line', 'third line', '']
app.disconnect(lid)
@pytest.mark.usefixtures('setup_test')
def test_new_documenter():
logging.setup(app, app._status, app._warning)
class MyDocumenter(ModuleLevelDocumenter):
objtype = 'integer'
directivetype = 'data'
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)
def assert_result_contains(item, objtype, name, **kw):
app._warning.truncate(0)
inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw)
# print '\n'.join(directive.result)
assert app._warning.getvalue() == ''
assert item in directive.result
del directive.result[:]
options.members = ['integer']
assert_result_contains('.. py:data:: integer', 'module', 'target')
@pytest.mark.usefixtures('setup_test')
def test_attrgetter_using():
from target import Class
def assert_getter_works(objtype, name, obj, attrs=[], **kw):
getattr_spy = []
def special_getattr(obj, name, *defargs):
if name in attrs:
getattr_spy.append((obj, name))
return None
return getattr(obj, name, *defargs)
AutoDirective._special_attrgetters[type] = special_getattr
del getattr_spy[:]
inst = app.registry.documenters[objtype](directive, name)
inst.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, \
'%r was not hooked by special_attrgetter function' % fullname
with catch_warnings(record=True):
options.members = ALL
options.inherited_members = False
assert_getter_works('class', 'target.Class', Class, ['meth'])
options.inherited_members = True
assert_getter_works('class', 'target.Class', Class, ['meth', 'inheritedmeth'])
@pytest.mark.usefixtures('setup_test')
def test_generate():
def assert_result_contains(item, objtype, name, **kw):
inst = app.registry.documenters[objtype](directive, name)
inst.generate(**kw)
assert item in directive.result
del directive.result[:]
# test auto and given content mixing
directive.env.ref_context['py:module'] = 'target'
assert_result_contains(' Function.', 'method', 'Class.meth')
add_content = ViewList()
add_content.append('Content.', '', 0)
assert_result_contains(' Function.', 'method',
'Class.meth', more_content=add_content)
assert_result_contains(' Content.', 'method',
'Class.meth', more_content=add_content)
@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.Base')
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
]
# default ALL-members
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:classmethod:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedmeth()',
' .. py:staticmethod:: Base.inheritedstaticmeth(cls)'
]
# default specific-members
options = {"members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:method:: Base.inheritedmeth()',
' .. py:staticmethod:: 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.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:classmethod:: Base.inheritedclassmeth()'
]
# members vs exclude-members
options = {"members": "inheritedmeth",
"exclude-members": "inheritedmeth"}
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
]
@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.descr',
' .. 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:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
ROGER_METHOD,
' .. 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_inherited_members(app):
options = {"members": None,
"inherited-members": None}
actual = do_autodoc(app, 'class', 'target.Class', options)
assert list(filter(lambda l: 'method::' in l, actual)) == [
' .. py:method:: Class.excludemeth()',
' .. py:classmethod:: Class.inheritedclassmeth()',
' .. py:method:: Class.inheritedmeth()',
' .. py:staticmethod:: Class.inheritedstaticmeth(cls)',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.skipmeth()'
]
@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:: add_documenter(cls)' in actual
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_special_members(app):
# 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:method:: Class.__init__(arg)',
' .. py:attribute:: Class.__module__',
' .. py:method:: Class.__special1__()',
' .. py:method:: Class.__special2__()',
' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.descr',
' .. 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:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
ROGER_METHOD,
' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
' .. py:attribute:: Class.udocattr',
' .. py:method:: Class.undocmeth()'
]
# specific special methods
options = {"members": None,
"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.descr',
' .. 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:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
ROGER_METHOD,
' .. 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_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:: CustomDataDescriptor(doc)',
'.. py:class:: CustomDataDescriptor2(doc)',
'.. py:class:: CustomDataDescriptorMeta',
'.. py:class:: CustomDict',
'.. py:class:: EnumCls',
'.. 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 = {"noindex": True}
actual = do_autodoc(app, 'module', 'target', options)
assert list(actual) == [
'',
'.. py:module:: target',
' :noindex:',
''
]
# TODO: :noindex: should be propagated to children of target item.
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(actual) == [
'',
'.. py:class:: Base',
' :noindex:',
' :module: target',
''
]
@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):
if PY3:
builtins = ' alias of :class:`builtins.dict`'
else:
builtins = ' alias of :class:`__builtin__.dict`'
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',
' ',
builtins
]
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',
' ',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_descriptor(app):
actual = do_autodoc(app, 'attribute', 'target.Class.descr')
assert list(actual) == [
'',
'.. py:attribute:: Class.descr',
' :module: target',
'',
' Descriptor instance docstring.',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_c_module(app):
actual = do_autodoc(app, 'function', 'time.asctime')
assert list(actual) == [
'',
'.. py:function:: asctime([tuple]) -> string',
' :module: time',
'',
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
' When the time tuple is not present, current time as returned by localtime()',
' is used.',
' '
]
@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": True,
'private-members': True}
actual = do_autodoc(app, 'class', 'target.Class', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Class(arg)',
' .. py:attribute:: Class.descr',
' .. 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.prop',
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr',
ROGER_METHOD,
' .. py:classmethod:: 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": True,
'private-members': True}
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:classmethod:: Class.moore(a, e, f) -> happiness',
ROGER_METHOD,
' .. py:method:: Class.skipmeth()',
' .. py:method:: Class.undocmeth()',
' .. py:attribute:: Class._private_inst_attr',
' .. py:attribute:: Class.attr',
' .. py:attribute:: Class.descr',
' .. 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.prop',
' .. py:attribute:: Class.skipattr',
' .. py:attribute:: Class.udocattr'
]
# case member-order=None
options = {"members": None,
"undoc-members": True,
'private-members': True}
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.descr',
' .. 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:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
ROGER_METHOD,
' .. 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_scope(app):
def convert(s):
return re.sub('<.*>', '<FILTERED>', s) # for py2/py3
app.env.temp_data['autodoc:module'] = 'target'
actual = do_autodoc(app, 'attribute', 'Class.mdocattr')
assert list(map(convert, actual)) == [
u'',
u'.. py:attribute:: Class.mdocattr',
u' :module: target',
u' :annotation: = <FILTERED>',
u'',
u' should be documented as well - süß',
u' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_class_scope(app):
def convert(s):
return re.sub('<.*>', '<FILTERED>', s) # for py2/py3
app.env.temp_data['autodoc:module'] = 'target'
app.env.temp_data['autodoc:class'] = 'Class'
actual = do_autodoc(app, 'attribute', 'mdocattr')
assert list(map(convert, actual)) == [
u'',
u'.. py:attribute:: Class.mdocattr',
u' :module: target',
u' :annotation: = <FILTERED>',
u'',
u' should be documented as well - süß',
u' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_docstring_signature(app):
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
'.. py:class:: DocstringSig',
' :module: target',
'',
' ',
' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ',
' :module: target',
' ',
' First line of docstring',
' ',
' rest of docstring',
' ',
' ',
' .. py:method:: DocstringSig.meth2()',
' :module: target',
' ',
' First line, no signature',
' Second line followed by indentation::',
' ',
' indented line',
' ',
' ',
' .. py:attribute:: DocstringSig.prop1',
' :module: target',
' ',
' First line of docstring',
' ',
' ',
' .. py:attribute:: DocstringSig.prop2',
' :module: target',
' ',
' First line of docstring',
' Second line of docstring',
' '
]
# disable autodoc_docstring_signature
app.config.autodoc_docstring_signature = False
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
u'',
u'.. py:class:: DocstringSig',
u' :module: target',
u'',
u' ',
u' .. py:method:: DocstringSig.meth()',
u' :module: target',
u' ',
u' meth(FOO, BAR=1) -> BAZ',
u' First line of docstring',
u' ',
u' rest of docstring',
u' ',
u' ',
u' ',
u' .. py:method:: DocstringSig.meth2()',
u' :module: target',
u' ',
u' First line, no signature',
u' Second line followed by indentation::',
u' ',
u' indented line',
u' ',
u' ',
u' .. py:attribute:: DocstringSig.prop1',
u' :module: target',
u' ',
u' DocstringSig.prop1(self)',
u' First line of docstring',
u' ',
u' ',
u' .. py:attribute:: DocstringSig.prop2',
u' :module: target',
u' ',
u' First line of docstring',
u' Second line of docstring',
u' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_class_attributes(app):
options = {"members": None,
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.AttCls', options)
assert list(actual) == [
'',
'.. py:class:: AttCls',
' :module: target',
'',
' ',
' .. py:attribute:: AttCls.a1',
' :module: target',
' :annotation: = hello world',
' ',
' ',
' .. py:attribute:: AttCls.a2',
' :module: target',
' :annotation: = None',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_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',
" :annotation: = 'a'",
' ',
' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.',
' ',
' ',
' .. py:attribute:: InstAttCls.ca2',
' :module: target',
" :annotation: = 'b'",
' ',
' Doc comment for InstAttCls.ca2. One line only.',
' ',
' ',
' .. py:attribute:: InstAttCls.ca3',
' :module: target',
" :annotation: = 'c'",
' ',
' Docstring for class attribute InstAttCls.ca3.',
' ',
' ',
' .. py:attribute:: InstAttCls.ia1',
' :module: target',
' :annotation: = None',
' ',
' Doc comment for instance attribute InstAttCls.ia1',
' ',
' ',
' .. py:attribute:: InstAttCls.ia2',
' :module: target',
' :annotation: = None',
' ',
' 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',
" :annotation: = 'a'",
' ',
' Doc comment for class attribute InstAttCls.ca1.',
' It can have multiple lines.',
' ',
' ',
' .. py:attribute:: InstAttCls.ia1',
' :module: target',
' :annotation: = None',
' ',
' Doc comment for instance attribute InstAttCls.ia1',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app):
options = {"members": None,
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.EnumCls', options)
assert list(actual) == [
'',
'.. py:class:: EnumCls',
' :module: target',
'',
' this is enum class',
' ',
' ',
' .. py:attribute:: EnumCls.val1',
' :module: target',
' :annotation: = 12',
' ',
' doc for val1',
' ',
' ',
' .. py:attribute:: EnumCls.val2',
' :module: target',
' :annotation: = 23',
' ',
' doc for val2',
' ',
' ',
' .. py:attribute:: EnumCls.val3',
' :module: target',
' :annotation: = 34',
' ',
' doc for val3',
' ',
' ',
' .. py:attribute:: EnumCls.val4',
' :module: target',
' :annotation: = 34',
' '
]
# checks for an attribute of EnumClass
actual = do_autodoc(app, 'attribute', 'target.EnumCls.val1')
assert list(actual) == [
'',
'.. py:attribute:: EnumCls.val1',
' :module: target',
' :annotation: = 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', options)
assert list(actual) == [
'',
'.. py:module:: target',
'',
'',
'.. py:class:: CustomDataDescriptor(doc)',
' :module: target',
'',
' Descriptor class docstring.',
' ',
' ',
' .. py:method:: CustomDataDescriptor.meth()',
' :module: target',
' ',
' Function.',
' ',
'',
'.. py:class:: CustomDataDescriptor2(doc)',
' :module: target',
'',
' Descriptor class with custom metaclass docstring.',
' '
]
@pytest.mark.sphinx('html', testroot='root')
def test_mocked_module_imports(app):
options = {"members": 'TestAutodoc,decoratedFunction'}
actual = do_autodoc(app, 'module', 'autodoc_missing_imports', options)
assert list(actual) == [
'',
'.. py:module:: autodoc_missing_imports',
'',
'',
'.. py:class:: TestAutodoc',
' :module: autodoc_missing_imports',
'',
' TestAutodoc docstring.',
' ',
' ',
' .. py:method:: TestAutodoc.decoratedMethod()',
' :module: autodoc_missing_imports',
' ',
' TestAutodoc::decoratedMethod docstring',
' ',
'',
'.. py:function:: decoratedFunction()',
' :module: autodoc_missing_imports',
'',
' decoratedFunction docstring',
' '
]
@pytest.mark.skipif(sys.version_info < (3, 4),
reason='functools.partialmethod is available on py34 or above')
@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() -> None',
' :module: target.partialmethod',
' ',
' Make a cell alive.',
' ',
' ',
' .. py:method:: Cell.set_dead() -> None',
' :module: target.partialmethod',
' ',
' Make a cell dead.',
' ',
' ',
' .. py:method:: Cell.set_state(state)',
' :module: target.partialmethod',
' ',
' Update state of cell to *state*.',
' ',
]
if (sys.version_info < (3, 5, 4) or
(3, 6, 5) <= sys.version_info < (3, 7) or
(3, 7, 0, 'beta', 3) <= sys.version_info):
# TODO: this condition should be updated after 3.7-final release.
expected = '\n'.join(expected).replace(' -> None', '').split('\n')
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_autodoc_default_flags__as_list__converted(app):
orig = [
'members',
'undoc-members',
('skipped', 1, 2),
{'also': 'skipped'},
]
expected = {
'members': None,
'undoc-members': None,
}
app.config.autodoc_default_flags = orig
convert_autodoc_default_flags(app, app.config)
assert app.config.autodoc_default_flags == expected
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_flags__as_dict__no_conversion(app):
orig = {
'members': 'this,that,other',
'undoc-members': None,
}
app.config.autodoc_default_flags = orig
convert_autodoc_default_flags(app, app.config)
assert app.config.autodoc_default_flags == orig
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_flags__with_flags(app):
# no settings
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' not in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: target.CustomIter' not in actual
# with :members:
app.config.autodoc_default_flags = {'members': None}
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
# with :members: and :undoc-members:
app.config.autodoc_default_flags = {
'members': None,
'undoc-members': None,
}
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' in actual
# with :special-members:
# Note that :members: must be *on* for :special-members: to work.
app.config.autodoc_default_flags = {
'members': None,
'special-members': None
}
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: CustomIter.__init__()' in actual
assert ' Create a new `CustomIter`.' in actual
assert ' .. py:method:: CustomIter.__iter__()' in actual
assert ' Iterate squares of each value.' in actual
if not IS_PYPY:
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
assert ' list of weak references to the object (if defined)' in actual
# :exclude-members: None - has no effect. Unlike :members:,
# :special-members:, etc. where None == "include all", here None means
# "no/false/off".
app.config.autodoc_default_flags = {
'members': None,
'exclude-members': None,
}
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
app.config.autodoc_default_flags = {
'members': None,
'special-members': None,
'exclude-members': None,
}
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: CustomIter.__init__()' in actual
assert ' Create a new `CustomIter`.' in actual
assert ' .. py:method:: CustomIter.__iter__()' in actual
assert ' Iterate squares of each value.' in actual
if not IS_PYPY:
assert ' .. py:attribute:: CustomIter.__weakref__' in actual
assert ' list of weak references to the object (if defined)' in actual
assert ' .. py:method:: CustomIter.snafucate()' in actual
assert ' Makes this snafucated.' in actual
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_flags__with_values(app):
# with :members:
app.config.autodoc_default_flags = {'members': 'val1,val2'}
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' in actual
assert ' .. py:attribute:: EnumCls.val2' in actual
assert ' .. py:attribute:: EnumCls.val3' not in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
# with :special-members:
# Note that :members: must be *on* for :special-members: to work.
app.config.autodoc_default_flags = {
'members': None,
'special-members': '__init__,__iter__',
}
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: CustomIter.__init__()' in actual
assert ' Create a new `CustomIter`.' in actual
assert ' .. py:method:: CustomIter.__iter__()' in actual
assert ' Iterate squares of each value.' in actual
if not IS_PYPY:
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
assert ' list of weak references to the object (if defined)' not in actual
# with :exclude-members:
app.config.autodoc_default_flags = {
'members': None,
'exclude-members': 'val1'
}
actual = do_autodoc(app, 'class', 'target.EnumCls')
assert ' .. py:attribute:: EnumCls.val1' not in actual
assert ' .. py:attribute:: EnumCls.val2' in actual
assert ' .. py:attribute:: EnumCls.val3' in actual
assert ' .. py:attribute:: EnumCls.val4' not in actual
app.config.autodoc_default_flags = {
'members': None,
'special-members': None,
'exclude-members': '__weakref__,snafucate',
}
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: CustomIter.__init__()' in actual
assert ' Create a new `CustomIter`.' in actual
assert ' .. py:method:: CustomIter.__iter__()' in actual
assert ' Iterate squares of each value.' in actual
if not IS_PYPY:
assert ' .. py:attribute:: CustomIter.__weakref__' not in actual
assert ' list of weak references to the object (if defined)' not in actual
assert ' .. py:method:: CustomIter.snafucate()' not in actual
assert ' Makes this snafucated.' not in actual