fix #2379 - Keyword args rendered with type links, as normal parameters (configurable under napoleon_use_keyword option)

This commit is contained in:
Jan Duzinkiewicz 2016-03-15 21:24:16 +01:00
parent 15d89a2bfe
commit d8cd3ec4be
4 changed files with 102 additions and 18 deletions

View File

@ -371,6 +371,22 @@ enabled in `conf.py`::
* **arg2** (*int, optional*) --
Description of `arg2`, defaults to 0
.. confval:: napoleon_use_keyword
True to use a ``:keyword:`` role for each function keyword argument.
False to use a single ``:keyword arguments:`` role for all the
keywords.
*Defaults to True.*
This behaves similarly to :attr:`napoleon_use_param`. Note unlike docutils,
``:keyword:`` and ``:param:`` will not be treated the same way - there will
be a separate "Keyword Arguments" section, rendered in the same fashion as
"Parameters" section (type links created if possible)
.. seealso::
:attr:`napoleon_use_param`
.. confval:: napoleon_use_rtype
True to use the ``:rtype:`` role for the return type. False to output

View File

@ -41,6 +41,7 @@ class Config(object):
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_use_keyword = True
.. _Google style:
http://google.github.io/styleguide/pyguide.html
@ -184,6 +185,20 @@ class Config(object):
* **arg2** (*int, optional*) --
Description of `arg2`, defaults to 0
napoleon_use_keyword : bool, defaults to True
True to use a ``:keyword:`` role for each function keyword argument.
False to use a single ``:keyword arguments:`` role for all the
keywords.
This behaves similarly to :attr:`napoleon_use_param`. Note unlike docutils,
``:keyword:`` and ``:param:`` will not be treated the same way - there will
be a separate "Keyword Arguments" section, rendered in the same fashion as
"Parameters" section (type links created if possible)
See Also
--------
:attr:`napoleon_use_param`
napoleon_use_rtype : bool, defaults to True
True to use the ``:rtype:`` role for the return type. False to output
the return type inline with the description.
@ -216,6 +231,7 @@ class Config(object):
'napoleon_use_ivar': (False, 'env'),
'napoleon_use_param': (True, 'env'),
'napoleon_use_rtype': (True, 'env'),
'napoleon_use_keyword': (True, 'env')
}
def __init__(self, **settings):
@ -251,6 +267,8 @@ def setup(app):
if not isinstance(app, Sphinx):
return # probably called by tests
_patch_python_domain()
app.connect('autodoc-process-docstring', _process_docstring)
app.connect('autodoc-skip-member', _skip_member)
@ -259,6 +277,23 @@ def setup(app):
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
def _patch_python_domain():
import sphinx.domains.python
from sphinx.domains.python import PyTypedField
import sphinx.locale
l_ = sphinx.locale.lazy_gettext
for doc_field in sphinx.domains.python.PyObject.doc_field_types:
if doc_field.name == 'parameter':
doc_field.names = ('param', 'parameter', 'arg', 'argument')
break
sphinx.domains.python.PyObject.doc_field_types.append(
PyTypedField('keyword', label=l_('Keyword Arguments'),
names=('keyword', 'kwarg', 'kwparam'),
typerolename='obj', typenames=('paramtype', 'kwtype'),
can_collapse=True),
)
def _process_docstring(app, what, name, obj, options, lines):
"""Process the docstring for a given python object.

View File

@ -527,7 +527,15 @@ class GoogleDocstring(UnicodeMixin):
return [header, '']
def _parse_keyword_arguments_section(self, section):
return self._format_fields('Keyword Arguments', self._consume_fields())
fields = self._consume_fields()
if self._config.napoleon_use_keyword:
return self._generate_docutils_params(
fields,
field_role="keyword",
type_role="kwtype"
)
else:
return self._format_fields('Keyword Arguments', fields)
def _parse_methods_section(self, section):
lines = []
@ -552,24 +560,26 @@ class GoogleDocstring(UnicodeMixin):
def _parse_parameters_section(self, section):
fields = self._consume_fields()
if self._config.napoleon_use_param:
lines = []
for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
if any(_desc):
if self._is_list(_desc):
_desc = [''] + _desc
field = ':param %s: ' % _name
lines.extend(self._format_block(field, _desc))
else:
lines.append(':param %s:' % _name)
if _type:
lines.append(':type %s: %s' % (_name, _type))
return lines + ['']
return self._generate_docutils_params(fields)
else:
return self._format_fields('Parameters', fields)
def _generate_docutils_params(self, fields, field_role='param', type_role='type'):
lines = []
for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
if any(_desc):
if self._is_list(_desc):
_desc = [''] + _desc
field = ':%s %s: ' % (field_role, _name)
lines.extend(self._format_block(field, _desc))
else:
lines.append(':%s %s:' % (field_role, _name))
if _type:
lines.append(':%s %s: %s' % (type_role, _name, _type))
return lines + ['']
def _parse_raises_section(self, section):
fields = self._consume_fields(parse_type=False, prefer_type=True)
field_type = ':raises:'

View File

@ -249,7 +249,11 @@ class GoogleDocstringTest(BaseDocstringTest):
)]
def test_docstrings(self):
config = Config(napoleon_use_param=False, napoleon_use_rtype=False)
config = Config(
napoleon_use_param=False,
napoleon_use_rtype=False,
napoleon_use_keyword=False
)
for docstring, expected in self.docstrings:
actual = str(GoogleDocstring(dedent(docstring), config))
expected = dedent(expected)
@ -1046,7 +1050,10 @@ class NumpyDocstringTest(BaseDocstringTest):
)]
def test_docstrings(self):
config = Config(napoleon_use_param=False, napoleon_use_rtype=False)
config = Config(
napoleon_use_param=False,
napoleon_use_rtype=False,
napoleon_use_keyword=False)
for docstring, expected in self.docstrings:
actual = str(NumpyDocstring(dedent(docstring), config))
expected = dedent(expected)
@ -1736,3 +1743,19 @@ definition_after_normal_text : int
config = Config(napoleon_use_param=False)
actual = str(NumpyDocstring(docstring, config))
self.assertEqual(expected, actual)
def test_keywords_with_types(self):
docstring = """\
Do as you please
Keyword Args:
gotham_is_yours (None): shall interfere.
"""
actual = str(GoogleDocstring(docstring))
expected = """\
Do as you please
:keyword gotham_is_yours: shall interfere.
:kwtype gotham_is_yours: None
"""
self.assertEqual(expected, actual)