autodoc: Use logging module to emit warnings

This commit is contained in:
Takeshi KOMIYA 2017-12-18 22:09:29 +09:00
parent 4173a0bfe1
commit 7a194f5296
4 changed files with 51 additions and 55 deletions

View File

@ -13,6 +13,8 @@ Incompatible changes
* #3929: apidoc: Move sphinx.apidoc to sphinx.ext.apidoc * #3929: apidoc: Move sphinx.apidoc to sphinx.ext.apidoc
* #4226: apidoc: Generate new style makefile (make-mode) * #4226: apidoc: Generate new style makefile (make-mode)
* #4274: sphinx-build returns 2 as an exit code on argument error * #4274: sphinx-build returns 2 as an exit code on argument error
* autodoc does not generate warnings messages to the generated document even if
:confval:`keep_warnings` is True. They are only emitted to stderr.
Deprecated Deprecated
---------- ----------

View File

@ -354,8 +354,7 @@ class Documenter(object):
explicit_modname, path, base, args, retann = \ explicit_modname, path, base, args, retann = \
py_ext_sig_re.match(self.name).groups() # type: ignore py_ext_sig_re.match(self.name).groups() # type: ignore
except AttributeError: except AttributeError:
self.directive.warn('invalid signature for auto%s (%r)' % logger.warning('invalid signature for auto%s (%r)' % (self.objtype, self.name))
(self.objtype, self.name))
return False return False
# support explicit module and class name separation via :: # support explicit module and class name separation via ::
@ -432,8 +431,7 @@ class Documenter(object):
if PY2: if PY2:
errmsg = errmsg.decode('utf-8') # type: ignore errmsg = errmsg.decode('utf-8') # type: ignore
logger.debug(errmsg) logger.warning(errmsg)
self.directive.warn(errmsg)
self.env.note_reread() self.env.note_reread()
return False return False
@ -493,8 +491,8 @@ class Documenter(object):
try: try:
args = self.format_args() args = self.format_args()
except Exception as err: except Exception as err:
self.directive.warn('error while formatting arguments for ' logger.warning('error while formatting arguments for %s: %s' %
'%s: %s' % (self.fullname, err)) (self.fullname, err))
args = None args = None
retann = self.retann retann = self.retann
@ -623,8 +621,8 @@ class Documenter(object):
members.append((mname, self.get_attr(self.object, mname))) members.append((mname, self.get_attr(self.object, mname)))
except AttributeError: except AttributeError:
if mname not in analyzed_member_names: if mname not in analyzed_member_names:
self.directive.warn('missing attribute %s in object %s' logger.warning('missing attribute %s in object %s' %
% (mname, self.fullname)) (mname, self.fullname))
elif self.options.inherited_members: elif self.options.inherited_members:
# safe_getmembers() uses dir() which pulls in members from all # safe_getmembers() uses dir() which pulls in members from all
# base classes # base classes
@ -820,11 +818,11 @@ class Documenter(object):
""" """
if not self.parse_name(): if not self.parse_name():
# need a module to import # need a module to import
self.directive.warn( logger.warning(
'don\'t know which module to import for autodocumenting ' 'don\'t know which module to import for autodocumenting '
'%r (try placing a "module" or "currentmodule" directive ' '%r (try placing a "module" or "currentmodule" directive '
'in the document, or giving an explicit module name)' 'in the document, or giving an explicit module name)' %
% self.name) self.name)
return return
# now, import the module and get object to document # now, import the module and get object to document
@ -910,15 +908,15 @@ class ModuleDocumenter(Documenter):
def resolve_name(self, modname, parents, path, base): def resolve_name(self, modname, parents, path, base):
# type: (str, Any, str, Any) -> Tuple[str, List[unicode]] # type: (str, Any, str, Any) -> Tuple[str, List[unicode]]
if modname is not None: if modname is not None:
self.directive.warn('"::" in automodule name doesn\'t make sense') logger.warning('"::" in automodule name doesn\'t make sense')
return (path or '') + base, [] return (path or '') + base, []
def parse_name(self): def parse_name(self):
# type: () -> bool # type: () -> bool
ret = Documenter.parse_name(self) ret = Documenter.parse_name(self)
if self.args or self.retann: if self.args or self.retann:
self.directive.warn('signature arguments or return annotation ' logger.warning('signature arguments or return annotation '
'given for automodule %s' % self.fullname) 'given for automodule %s' % self.fullname)
return ret return ret
def add_directive_header(self, sig): def add_directive_header(self, sig):
@ -949,7 +947,7 @@ class ModuleDocumenter(Documenter):
# Sometimes __all__ is broken... # Sometimes __all__ is broken...
if not isinstance(memberlist, (list, tuple)) or not \ if not isinstance(memberlist, (list, tuple)) or not \
all(isinstance(entry, string_types) for entry in memberlist): all(isinstance(entry, string_types) for entry in memberlist):
self.directive.warn( logger.warning(
'__all__ should be a list of strings, not %r ' '__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__' % '(in module %s) -- ignoring __all__' %
(memberlist, self.fullname)) (memberlist, self.fullname))
@ -962,10 +960,10 @@ class ModuleDocumenter(Documenter):
try: try:
ret.append((mname, safe_getattr(self.object, mname))) ret.append((mname, safe_getattr(self.object, mname)))
except AttributeError: except AttributeError:
self.directive.warn( logger.warning(
'missing attribute mentioned in :members: or __all__: ' 'missing attribute mentioned in :members: or __all__: '
'module %s, attribute %s' % ( 'module %s, attribute %s' %
safe_getattr(self.object, '__name__', '???'), mname)) (safe_getattr(self.object, '__name__', '???'), mname))
return False, ret return False, ret
@ -1542,7 +1540,7 @@ class AutoDirective(Directive):
def warn(self, msg): def warn(self, msg):
# type: (unicode) -> None # type: (unicode) -> None
self.warnings.append(self.reporter.warning(msg, line=self.lineno)) logger.warning(msg, line=self.lineno)
def run(self): def run(self):
# type: () -> List[nodes.Node] # type: () -> List[nodes.Node]
@ -1550,7 +1548,6 @@ class AutoDirective(Directive):
# a set of dependent filenames # a set of dependent filenames
self.reporter = self.state.document.reporter self.reporter = self.state.document.reporter
self.env = self.state.document.settings.env self.env = self.state.document.settings.env
self.warnings = [] # type: List[unicode]
self.result = ViewList() self.result = ViewList()
try: try:
@ -1585,7 +1582,7 @@ class AutoDirective(Directive):
documenter = doc_class(self, self.arguments[0]) documenter = doc_class(self, self.arguments[0])
documenter.generate(more_content=self.content) documenter.generate(more_content=self.content)
if not self.result: if not self.result:
return self.warnings return []
logger.debug('[autodoc] output:\n%s', '\n'.join(self.result)) logger.debug('[autodoc] output:\n%s', '\n'.join(self.result))
@ -1610,7 +1607,7 @@ class AutoDirective(Directive):
node.document = self.state.document node.document = self.state.document
self.state.nested_parse(self.result, 0, node) self.state.nested_parse(self.result, 0, node)
self.state.memo.reporter = old_reporter self.state.memo.reporter = old_reporter
return self.warnings + node.children return node.children
def add_documenter(cls): def add_documenter(cls):

View File

@ -21,6 +21,7 @@ from docutils.statemachine import ViewList
from sphinx.ext.autodoc import AutoDirective, add_documenter, \ from sphinx.ext.autodoc import AutoDirective, add_documenter, \
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
from sphinx.util import logging
app = None app = None
@ -30,7 +31,7 @@ def setup_module(rootdir, sphinx_test_tempdir):
global app global app
srcdir = sphinx_test_tempdir / 'autodoc-root' srcdir = sphinx_test_tempdir / 'autodoc-root'
if not srcdir.exists(): if not srcdir.exists():
(rootdir/'test-root').copytree(srcdir) (rootdir / 'test-root').copytree(srcdir)
app = SphinxTestApp(srcdir=srcdir) app = SphinxTestApp(srcdir=srcdir)
app.builder.env.app = app app.builder.env.app = app
app.builder.env.temp_data['docname'] = 'dummy' app.builder.env.temp_data['docname'] = 'dummy'
@ -47,7 +48,7 @@ directive = options = None
@pytest.fixture @pytest.fixture
def setup_test(): def setup_test():
global options, directive global options, directive
global processed_docstrings, processed_signatures, _warnings global processed_docstrings, processed_signatures
options = Struct( options = Struct(
inherited_members = False, inherited_members = False,
@ -70,24 +71,17 @@ def setup_test():
env = app.builder.env, env = app.builder.env,
genopt = options, genopt = options,
result = ViewList(), result = ViewList(),
warn = warnfunc,
filename_set = set(), filename_set = set(),
) )
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
_warnings = []
_warnings = []
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
def warnfunc(msg):
_warnings.append(msg)
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
processed_docstrings.append((what, name)) processed_docstrings.append((what, name))
if name == 'bar': if name == 'bar':
@ -111,20 +105,21 @@ def skip_member(app, what, name, obj, skip, options):
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_generate(): def test_generate():
logging.setup(app, app._status, app._warning)
def assert_warns(warn_str, objtype, name, **kw): def assert_warns(warn_str, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert len(directive.result) == 0, directive.result assert len(directive.result) == 0, directive.result
assert len(_warnings) == 1, _warnings assert warn_str in app._warning.getvalue()
assert warn_str in _warnings[0], _warnings app._warning.truncate(0)
del _warnings[:]
def assert_works(objtype, name, **kw): def assert_works(objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert directive.result assert directive.result
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
del directive.result[:] del directive.result[:]
def assert_processes(items, objtype, name, **kw): def assert_processes(items, objtype, name, **kw):
@ -137,7 +132,7 @@ def test_generate():
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
@ -145,7 +140,7 @@ def test_generate():
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.options.member_order = member_order inst.options.member_order = member_order
inst.generate(**kw) inst.generate(**kw)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
items = list(reversed(items)) items = list(reversed(items))
lineiter = iter(directive.result) lineiter = iter(directive.result)
# for line in directive.result: # for line in directive.result:

View File

@ -21,6 +21,7 @@ from docutils.statemachine import ViewList
from sphinx.ext.autodoc import AutoDirective, add_documenter, \ from sphinx.ext.autodoc import AutoDirective, add_documenter, \
ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL
from sphinx.util import logging
app = None app = None
@ -47,7 +48,7 @@ directive = options = None
@pytest.fixture @pytest.fixture
def setup_test(): def setup_test():
global options, directive global options, directive
global processed_docstrings, processed_signatures, _warnings global processed_docstrings, processed_signatures
options = Struct( options = Struct(
inherited_members = False, inherited_members = False,
@ -70,28 +71,24 @@ def setup_test():
env = app.builder.env, env = app.builder.env,
genopt = options, genopt = options,
result = ViewList(), result = ViewList(),
warn = warnfunc,
filename_set = set(), filename_set = set(),
) )
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
_warnings = []
app._status.truncate(0)
app._warning.truncate(0)
yield yield
AutoDirective._special_attrgetters.clear() AutoDirective._special_attrgetters.clear()
_warnings = []
processed_docstrings = [] processed_docstrings = []
processed_signatures = [] processed_signatures = []
def warnfunc(msg):
_warnings.append(msg)
def process_docstring(app, what, name, obj, options, lines): def process_docstring(app, what, name, obj, options, lines):
processed_docstrings.append((what, name)) processed_docstrings.append((what, name))
if name == 'bar': if name == 'bar':
@ -115,6 +112,8 @@ def skip_member(app, what, name, obj, skip, options):
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_parse_name(): def test_parse_name():
logging.setup(app, app._status, app._warning)
def verify(objtype, name, result): def verify(objtype, name, result):
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
assert inst.parse_name() assert inst.parse_name()
@ -124,8 +123,7 @@ def test_parse_name():
verify('module', 'test_autodoc', ('test_autodoc', [], None, None)) verify('module', 'test_autodoc', ('test_autodoc', [], None, None))
verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None)) verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None))
verify('module', 'test(arg)', ('test', [], 'arg', None)) verify('module', 'test(arg)', ('test', [], 'arg', None))
assert 'signature arguments' in _warnings[0] assert 'signature arguments' in app._warning.getvalue()
del _warnings[:]
# for functions/classes # for functions/classes
verify('function', 'test_autodoc.raises', verify('function', 'test_autodoc.raises',
@ -241,7 +239,6 @@ def test_format_signature():
# test exception handling (exception is caught and args is '') # test exception handling (exception is caught and args is '')
directive.env.config.autodoc_docstring_signature = False directive.env.config.autodoc_docstring_signature = False
assert formatsig('function', 'int', int, None, None) == '' assert formatsig('function', 'int', int, None, None) == ''
del _warnings[:]
# test processing by event handler # test processing by event handler
assert formatsig('method', 'bar', H.foo1, None, None) == '42' assert formatsig('method', 'bar', H.foo1, None, None) == '42'
@ -533,6 +530,8 @@ def test_docstring_property_processing():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_new_documenter(): def test_new_documenter():
logging.setup(app, app._status, app._warning)
class MyDocumenter(ModuleLevelDocumenter): class MyDocumenter(ModuleLevelDocumenter):
objtype = 'integer' objtype = 'integer'
directivetype = 'data' directivetype = 'data'
@ -548,10 +547,11 @@ def test_new_documenter():
add_documenter(MyDocumenter) add_documenter(MyDocumenter)
def assert_result_contains(item, objtype, name, **kw): def assert_result_contains(item, objtype, name, **kw):
app._warning.truncate(0)
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
@ -593,20 +593,22 @@ def test_attrgetter_using():
@pytest.mark.usefixtures('setup_test') @pytest.mark.usefixtures('setup_test')
def test_generate(): def test_generate():
logging.setup(app, app._status, app._warning)
def assert_warns(warn_str, objtype, name, **kw): def assert_warns(warn_str, objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert len(directive.result) == 0, directive.result assert len(directive.result) == 0, directive.result
assert len(_warnings) == 1, _warnings
assert warn_str in _warnings[0], _warnings assert warn_str in app._warning.getvalue()
del _warnings[:] app._warning.truncate(0)
def assert_works(objtype, name, **kw): def assert_works(objtype, name, **kw):
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert directive.result assert directive.result
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
del directive.result[:] del directive.result[:]
def assert_processes(items, objtype, name, **kw): def assert_processes(items, objtype, name, **kw):
@ -619,7 +621,7 @@ def test_generate():
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
# print '\n'.join(directive.result) # print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
assert item in directive.result assert item in directive.result
del directive.result[:] del directive.result[:]
@ -627,7 +629,7 @@ def test_generate():
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.options.member_order = member_order inst.options.member_order = member_order
inst.generate(**kw) inst.generate(**kw)
assert len(_warnings) == 0, _warnings assert app._warning.getvalue() == ''
items = list(reversed(items)) items = list(reversed(items))
lineiter = iter(directive.result) lineiter = iter(directive.result)
# for line in directive.result: # for line in directive.result: