diff --git a/doc/ext/napoleon.rst b/doc/ext/napoleon.rst index 304d8ac227..2f2fb43761 100644 --- a/doc/ext/napoleon.rst +++ b/doc/ext/napoleon.rst @@ -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 diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index 85c8acec8c..8dddc127a5 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -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. diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 742a9fbbae..c12b75e6d1 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -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:' diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 17c1a7a56c..ece55ebe46 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -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)