summaryrefslogtreecommitdiffstats
path: root/tests/test_extensions/test_ext_napoleon_docstring.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_extensions/test_ext_napoleon_docstring.py')
-rw-r--r--tests/test_extensions/test_ext_napoleon_docstring.py2703
1 files changed, 2703 insertions, 0 deletions
diff --git a/tests/test_extensions/test_ext_napoleon_docstring.py b/tests/test_extensions/test_ext_napoleon_docstring.py
new file mode 100644
index 0000000..d7ef489
--- /dev/null
+++ b/tests/test_extensions/test_ext_napoleon_docstring.py
@@ -0,0 +1,2703 @@
+"""Tests for :mod:`sphinx.ext.napoleon.docstring` module."""
+
+import re
+import zlib
+from collections import namedtuple
+from inspect import cleandoc
+from itertools import product
+from textwrap import dedent
+from unittest import mock
+
+import pytest
+
+from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
+from sphinx.ext.napoleon import Config
+from sphinx.ext.napoleon.docstring import (
+ GoogleDocstring,
+ NumpyDocstring,
+ _convert_numpy_type_spec,
+ _recombine_set_tokens,
+ _token_type,
+ _tokenize_type_spec,
+)
+from sphinx.testing.util import etree_parse
+
+from tests.test_extensions.ext_napoleon_pep526_data_google import PEP526GoogleClass
+from tests.test_extensions.ext_napoleon_pep526_data_numpy import PEP526NumpyClass
+
+
+class NamedtupleSubclass(namedtuple('NamedtupleSubclass', ('attr1', 'attr2'))):
+ """Sample namedtuple subclass
+
+ Attributes
+ ----------
+ attr1 : Arbitrary type
+ Quick description of attr1
+ attr2 : Another arbitrary type
+ Quick description of attr2
+ attr3 : Type
+
+ Adds a newline after the type
+
+ """
+
+ # To avoid creating a dict, as a namedtuple doesn't have it:
+ __slots__ = ()
+
+ def __new__(cls, attr1, attr2=None):
+ return super().__new__(cls, attr1, attr2)
+
+
+class TestNamedtupleSubclass:
+ def test_attributes_docstring(self):
+ config = Config()
+ actual = str(NumpyDocstring(cleandoc(NamedtupleSubclass.__doc__),
+ config=config, app=None, what='class',
+ name='NamedtupleSubclass', obj=NamedtupleSubclass))
+ expected = """\
+Sample namedtuple subclass
+
+.. attribute:: attr1
+
+ Quick description of attr1
+
+ :type: Arbitrary type
+
+.. attribute:: attr2
+
+ Quick description of attr2
+
+ :type: Another arbitrary type
+
+.. attribute:: attr3
+
+ Adds a newline after the type
+
+ :type: Type
+"""
+
+ assert expected == actual
+
+
+class TestInlineAttribute:
+ inline_google_docstring = ('inline description with '
+ '``a : in code``, '
+ 'a :ref:`reference`, '
+ 'a `link <https://foo.bar>`_, '
+ 'a :meta public:, '
+ 'a :meta field: value and '
+ 'an host:port and HH:MM strings.')
+
+ @staticmethod
+ def _docstring(source):
+ rst = GoogleDocstring(source, config=Config(), app=None, what='attribute', name='some_data', obj=0)
+ return str(rst)
+
+ def test_class_data_member(self):
+ source = 'data member description:\n\n- a: b'
+ actual = self._docstring(source).splitlines()
+ assert actual == ['data member description:', '', '- a: b']
+
+ def test_class_data_member_inline(self):
+ source = f'CustomType: {self.inline_google_docstring}'
+ actual = self._docstring(source).splitlines()
+ assert actual == [self.inline_google_docstring, '', ':type: CustomType']
+
+ def test_class_data_member_inline_no_type(self):
+ source = self.inline_google_docstring
+ actual = self._docstring(source).splitlines()
+ assert actual == [source]
+
+ def test_class_data_member_inline_ref_in_type(self):
+ source = f':class:`int`: {self.inline_google_docstring}'
+ actual = self._docstring(source).splitlines()
+ assert actual == [self.inline_google_docstring, '', ':type: :class:`int`']
+
+
+class TestGoogleDocstring:
+ docstrings = [(
+ """Single line summary""",
+ """Single line summary""",
+ ), (
+ """
+ Single line summary
+
+ Extended description
+
+ """,
+ """
+ Single line summary
+
+ Extended description
+ """,
+ ), (
+ """
+ Single line summary
+
+ Args:
+ arg1(str):Extended
+ description of arg1
+ """,
+ """
+ Single line summary
+
+ :Parameters: **arg1** (*str*) -- Extended
+ description of arg1
+ """,
+ ), (
+ """
+ Single line summary
+
+ Args:
+ arg1(str):Extended
+ description of arg1
+ arg2 ( int ) : Extended
+ description of arg2
+
+ Keyword Args:
+ kwarg1(str):Extended
+ description of kwarg1
+ kwarg2 ( int ) : Extended
+ description of kwarg2""",
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (*str*) -- Extended
+ description of arg1
+ * **arg2** (*int*) -- Extended
+ description of arg2
+
+ :Keyword Arguments: * **kwarg1** (*str*) -- Extended
+ description of kwarg1
+ * **kwarg2** (*int*) -- Extended
+ description of kwarg2
+ """,
+ ), (
+ """
+ Single line summary
+
+ Arguments:
+ arg1(str):Extended
+ description of arg1
+ arg2 ( int ) : Extended
+ description of arg2
+
+ Keyword Arguments:
+ kwarg1(str):Extended
+ description of kwarg1
+ kwarg2 ( int ) : Extended
+ description of kwarg2""",
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (*str*) -- Extended
+ description of arg1
+ * **arg2** (*int*) -- Extended
+ description of arg2
+
+ :Keyword Arguments: * **kwarg1** (*str*) -- Extended
+ description of kwarg1
+ * **kwarg2** (*int*) -- Extended
+ description of kwarg2
+ """,
+ ), (
+ """
+ Single line summary
+
+ Return:
+ str:Extended
+ description of return value
+ """,
+ """
+ Single line summary
+
+ :returns: *str* -- Extended
+ description of return value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Returns:
+ str:Extended
+ description of return value
+ """,
+ """
+ Single line summary
+
+ :returns: *str* -- Extended
+ description of return value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Returns:
+ Extended
+ description of return value
+ """,
+ """
+ Single line summary
+
+ :returns: Extended
+ description of return value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Returns:
+ Extended
+ """,
+ """
+ Single line summary
+
+ :returns: Extended
+ """,
+ ), (
+ """
+ Single line summary
+
+ Args:
+ arg1(str):Extended
+ description of arg1
+ *args: Variable length argument list.
+ **kwargs: Arbitrary keyword arguments.
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (*str*) -- Extended
+ description of arg1
+ * **\\*args** -- Variable length argument list.
+ * **\\*\\*kwargs** -- Arbitrary keyword arguments.
+ """,
+ ), (
+ """
+ Single line summary
+
+ Args:
+ arg1 (list(int)): Description
+ arg2 (list[int]): Description
+ arg3 (dict(str, int)): Description
+ arg4 (dict[str, int]): Description
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (*list(int)*) -- Description
+ * **arg2** (*list[int]*) -- Description
+ * **arg3** (*dict(str, int)*) -- Description
+ * **arg4** (*dict[str, int]*) -- Description
+ """,
+ ), (
+ """
+ Single line summary
+
+ Receive:
+ arg1 (list(int)): Description
+ arg2 (list[int]): Description
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (*list(int)*) -- Description
+ * **arg2** (*list[int]*) -- Description
+ """,
+ ), (
+ """
+ Single line summary
+
+ Receives:
+ arg1 (list(int)): Description
+ arg2 (list[int]): Description
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (*list(int)*) -- Description
+ * **arg2** (*list[int]*) -- Description
+ """,
+ ), (
+ """
+ Single line summary
+
+ Yield:
+ str:Extended
+ description of yielded value
+ """,
+ """
+ Single line summary
+
+ :Yields: *str* -- Extended
+ description of yielded value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Yields:
+ Extended
+ description of yielded value
+ """,
+ """
+ Single line summary
+
+ :Yields: Extended
+ description of yielded value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Args:
+
+ arg1 (list of str): Extended
+ description of arg1.
+ arg2 (tuple of int): Extended
+ description of arg2.
+ arg3 (tuple of list of float): Extended
+ description of arg3.
+ arg4 (int, float, or list of bool): Extended
+ description of arg4.
+ arg5 (list of int, float, or bool): Extended
+ description of arg5.
+ arg6 (list of int or float): Extended
+ description of arg6.
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (*list of str*) -- Extended
+ description of arg1.
+ * **arg2** (*tuple of int*) -- Extended
+ description of arg2.
+ * **arg3** (*tuple of list of float*) -- Extended
+ description of arg3.
+ * **arg4** (*int, float, or list of bool*) -- Extended
+ description of arg4.
+ * **arg5** (*list of int, float, or bool*) -- Extended
+ description of arg5.
+ * **arg6** (*list of int or float*) -- Extended
+ description of arg6.
+ """,
+ )]
+
+ def test_sphinx_admonitions(self):
+ admonition_map = {
+ 'Attention': 'attention',
+ 'Caution': 'caution',
+ 'Danger': 'danger',
+ 'Error': 'error',
+ 'Hint': 'hint',
+ 'Important': 'important',
+ 'Note': 'note',
+ 'Tip': 'tip',
+ 'Todo': 'todo',
+ 'Warning': 'warning',
+ 'Warnings': 'warning',
+ }
+ config = Config()
+ for section, admonition in admonition_map.items():
+ # Multiline
+ actual = str(GoogleDocstring(f"{section}:\n"
+ " this is the first line\n"
+ "\n"
+ " and this is the second line\n",
+ config))
+ expect = (f".. {admonition}::\n"
+ "\n"
+ " this is the first line\n"
+ " \n"
+ " and this is the second line\n"
+ )
+ assert expect == actual
+
+ # Single line
+ actual = str(GoogleDocstring(f"{section}:\n"
+ " this is a single line\n",
+ config))
+ expect = f".. {admonition}:: this is a single line\n"
+ assert expect == actual
+
+ def test_docstrings(self):
+ 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)
+ assert expected == actual
+
+ def test_parameters_with_class_reference(self):
+ docstring = """\
+Construct a new XBlock.
+
+This class should only be used by runtimes.
+
+Arguments:
+ runtime (:class:`~typing.Dict`\\[:class:`int`,:class:`str`\\]): Use it to
+ access the environment. It is available in XBlock code
+ as ``self.runtime``.
+
+ field_data (:class:`FieldData`): Interface used by the XBlock
+ fields to access their data from wherever it is persisted.
+
+ scope_ids (:class:`ScopeIds`): Identifiers needed to resolve scopes.
+
+"""
+
+ actual = str(GoogleDocstring(docstring))
+ expected = """\
+Construct a new XBlock.
+
+This class should only be used by runtimes.
+
+:param runtime: Use it to
+ access the environment. It is available in XBlock code
+ as ``self.runtime``.
+:type runtime: :class:`~typing.Dict`\\[:class:`int`,:class:`str`\\]
+:param field_data: Interface used by the XBlock
+ fields to access their data from wherever it is persisted.
+:type field_data: :class:`FieldData`
+:param scope_ids: Identifiers needed to resolve scopes.
+:type scope_ids: :class:`ScopeIds`
+"""
+ assert expected == actual
+
+ def test_attributes_with_class_reference(self):
+ docstring = """\
+Attributes:
+ in_attr(:class:`numpy.ndarray`): super-dooper attribute
+"""
+
+ actual = str(GoogleDocstring(docstring))
+ expected = """\
+.. attribute:: in_attr
+
+ super-dooper attribute
+
+ :type: :class:`numpy.ndarray`
+"""
+ assert expected == actual
+
+ docstring = """\
+Attributes:
+ in_attr(numpy.ndarray): super-dooper attribute
+"""
+
+ actual = str(GoogleDocstring(docstring))
+ expected = """\
+.. attribute:: in_attr
+
+ super-dooper attribute
+
+ :type: numpy.ndarray
+"""
+
+ def test_attributes_with_use_ivar(self):
+ docstring = """\
+Attributes:
+ foo (int): blah blah
+ bar (str): blah blah
+"""
+
+ config = Config(napoleon_use_ivar=True)
+ actual = str(GoogleDocstring(docstring, config, obj=self.__class__))
+ expected = """\
+:ivar foo: blah blah
+:vartype foo: int
+:ivar bar: blah blah
+:vartype bar: str
+"""
+ assert expected == actual
+
+ def test_code_block_in_returns_section(self):
+ docstring = """
+Returns:
+ foobar: foo::
+
+ codecode
+ codecode
+"""
+ expected = """
+:returns:
+
+ foo::
+
+ codecode
+ codecode
+:rtype: foobar
+"""
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_colon_in_return_type(self):
+ docstring = """Example property.
+
+Returns:
+ :py:class:`~.module.submodule.SomeClass`: an example instance
+ if available, None if not available.
+"""
+ expected = """Example property.
+
+:returns: an example instance
+ if available, None if not available.
+:rtype: :py:class:`~.module.submodule.SomeClass`
+"""
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_xrefs_in_return_type(self):
+ docstring = """Example Function
+
+Returns:
+ :class:`numpy.ndarray`: A :math:`n \\times 2` array containing
+ a bunch of math items
+"""
+ expected = """Example Function
+
+:returns: A :math:`n \\times 2` array containing
+ a bunch of math items
+:rtype: :class:`numpy.ndarray`
+"""
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_raises_types(self):
+ docstrings = [("""
+Example Function
+
+Raises:
+ RuntimeError:
+ A setting wasn't specified, or was invalid.
+ ValueError:
+ Something something value error.
+ :py:class:`AttributeError`
+ errors for missing attributes.
+ ~InvalidDimensionsError
+ If the dimensions couldn't be parsed.
+ `InvalidArgumentsError`
+ If the arguments are invalid.
+ :exc:`~ValueError`
+ If the arguments are wrong.
+
+""", """
+Example Function
+
+:raises RuntimeError: A setting wasn't specified, or was invalid.
+:raises ValueError: Something something value error.
+:raises AttributeError: errors for missing attributes.
+:raises ~InvalidDimensionsError: If the dimensions couldn't be parsed.
+:raises InvalidArgumentsError: If the arguments are invalid.
+:raises ~ValueError: If the arguments are wrong.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ InvalidDimensionsError
+
+""", """
+Example Function
+
+:raises InvalidDimensionsError:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ Invalid Dimensions Error
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ Invalid Dimensions Error: With description
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error: With description
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ InvalidDimensionsError: If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises InvalidDimensionsError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ Invalid Dimensions Error: If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises If the dimensions couldn't be parsed.:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ :class:`exc.InvalidDimensionsError`
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ :class:`exc.InvalidDimensionsError`: If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ :class:`exc.InvalidDimensionsError`: If the dimensions couldn't be parsed,
+ then a :class:`exc.InvalidDimensionsError` will be raised.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed,
+ then a :class:`exc.InvalidDimensionsError` will be raised.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ :class:`exc.InvalidDimensionsError`: If the dimensions couldn't be parsed.
+ :class:`exc.InvalidArgumentsError`: If the arguments are invalid.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed.
+:raises exc.InvalidArgumentsError: If the arguments are invalid.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises:
+ :class:`exc.InvalidDimensionsError`
+ :class:`exc.InvalidArgumentsError`
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError:
+:raises exc.InvalidArgumentsError:
+""")]
+ for docstring, expected in docstrings:
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_kwargs_in_arguments(self):
+ docstring = """Allows to create attributes binded to this device.
+
+Some other paragraph.
+
+Code sample for usage::
+
+ dev.bind(loopback=Loopback)
+ dev.loopback.configure()
+
+Arguments:
+ **kwargs: name/class pairs that will create resource-managers
+ bound as instance attributes to this instance. See code
+ example above.
+"""
+ expected = """Allows to create attributes binded to this device.
+
+Some other paragraph.
+
+Code sample for usage::
+
+ dev.bind(loopback=Loopback)
+ dev.loopback.configure()
+
+:param \\*\\*kwargs: name/class pairs that will create resource-managers
+ bound as instance attributes to this instance. See code
+ example above.
+"""
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_section_header_formatting(self):
+ docstrings = [("""
+Summary line
+
+Example:
+ Multiline reStructuredText
+ literal code block
+
+""", """
+Summary line
+
+.. rubric:: Example
+
+Multiline reStructuredText
+literal code block
+"""),
+ ################################
+ ("""
+Summary line
+
+Example::
+
+ Multiline reStructuredText
+ literal code block
+
+""", """
+Summary line
+
+Example::
+
+ Multiline reStructuredText
+ literal code block
+"""),
+ ################################
+ ("""
+Summary line
+
+:Example:
+
+ Multiline reStructuredText
+ literal code block
+
+""", """
+Summary line
+
+:Example:
+
+ Multiline reStructuredText
+ literal code block
+""")]
+ for docstring, expected in docstrings:
+ actual = str(GoogleDocstring(docstring))
+ assert expected == actual
+
+ def test_list_in_parameter_description(self):
+ docstring = """One line summary.
+
+Parameters:
+ no_list (int):
+ one_bullet_empty (int):
+ *
+ one_bullet_single_line (int):
+ - first line
+ one_bullet_two_lines (int):
+ + first line
+ continued
+ two_bullets_single_line (int):
+ - first line
+ - second line
+ two_bullets_two_lines (int):
+ * first line
+ continued
+ * second line
+ continued
+ one_enumeration_single_line (int):
+ 1. first line
+ one_enumeration_two_lines (int):
+ 1) first line
+ continued
+ two_enumerations_one_line (int):
+ (iii) first line
+ (iv) second line
+ two_enumerations_two_lines (int):
+ a. first line
+ continued
+ b. second line
+ continued
+ one_definition_one_line (int):
+ item 1
+ first line
+ one_definition_two_lines (int):
+ item 1
+ first line
+ continued
+ two_definitions_one_line (int):
+ item 1
+ first line
+ item 2
+ second line
+ two_definitions_two_lines (int):
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+ one_definition_blank_line (int):
+ item 1
+
+ first line
+
+ extra first line
+
+ two_definitions_blank_lines (int):
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+
+ definition_after_inline_text (int): text line
+
+ item 1
+ first line
+
+ definition_after_normal_text (int):
+ text line
+
+ item 1
+ first line
+"""
+
+ expected = """One line summary.
+
+:param no_list:
+:type no_list: int
+:param one_bullet_empty:
+ *
+:type one_bullet_empty: int
+:param one_bullet_single_line:
+ - first line
+:type one_bullet_single_line: int
+:param one_bullet_two_lines:
+ + first line
+ continued
+:type one_bullet_two_lines: int
+:param two_bullets_single_line:
+ - first line
+ - second line
+:type two_bullets_single_line: int
+:param two_bullets_two_lines:
+ * first line
+ continued
+ * second line
+ continued
+:type two_bullets_two_lines: int
+:param one_enumeration_single_line:
+ 1. first line
+:type one_enumeration_single_line: int
+:param one_enumeration_two_lines:
+ 1) first line
+ continued
+:type one_enumeration_two_lines: int
+:param two_enumerations_one_line:
+ (iii) first line
+ (iv) second line
+:type two_enumerations_one_line: int
+:param two_enumerations_two_lines:
+ a. first line
+ continued
+ b. second line
+ continued
+:type two_enumerations_two_lines: int
+:param one_definition_one_line:
+ item 1
+ first line
+:type one_definition_one_line: int
+:param one_definition_two_lines:
+ item 1
+ first line
+ continued
+:type one_definition_two_lines: int
+:param two_definitions_one_line:
+ item 1
+ first line
+ item 2
+ second line
+:type two_definitions_one_line: int
+:param two_definitions_two_lines:
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+:type two_definitions_two_lines: int
+:param one_definition_blank_line:
+ item 1
+
+ first line
+
+ extra first line
+:type one_definition_blank_line: int
+:param two_definitions_blank_lines:
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+:type two_definitions_blank_lines: int
+:param definition_after_inline_text: text line
+
+ item 1
+ first line
+:type definition_after_inline_text: int
+:param definition_after_normal_text: text line
+
+ item 1
+ first line
+:type definition_after_normal_text: int
+"""
+ config = Config(napoleon_use_param=True)
+ actual = str(GoogleDocstring(docstring, config))
+ assert expected == actual
+
+ expected = """One line summary.
+
+:Parameters: * **no_list** (*int*)
+ * **one_bullet_empty** (*int*) --
+
+ *
+ * **one_bullet_single_line** (*int*) --
+
+ - first line
+ * **one_bullet_two_lines** (*int*) --
+
+ + first line
+ continued
+ * **two_bullets_single_line** (*int*) --
+
+ - first line
+ - second line
+ * **two_bullets_two_lines** (*int*) --
+
+ * first line
+ continued
+ * second line
+ continued
+ * **one_enumeration_single_line** (*int*) --
+
+ 1. first line
+ * **one_enumeration_two_lines** (*int*) --
+
+ 1) first line
+ continued
+ * **two_enumerations_one_line** (*int*) --
+
+ (iii) first line
+ (iv) second line
+ * **two_enumerations_two_lines** (*int*) --
+
+ a. first line
+ continued
+ b. second line
+ continued
+ * **one_definition_one_line** (*int*) --
+
+ item 1
+ first line
+ * **one_definition_two_lines** (*int*) --
+
+ item 1
+ first line
+ continued
+ * **two_definitions_one_line** (*int*) --
+
+ item 1
+ first line
+ item 2
+ second line
+ * **two_definitions_two_lines** (*int*) --
+
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+ * **one_definition_blank_line** (*int*) --
+
+ item 1
+
+ first line
+
+ extra first line
+ * **two_definitions_blank_lines** (*int*) --
+
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+ * **definition_after_inline_text** (*int*) -- text line
+
+ item 1
+ first line
+ * **definition_after_normal_text** (*int*) -- text line
+
+ item 1
+ first line
+"""
+ config = Config(napoleon_use_param=False)
+ actual = str(GoogleDocstring(docstring, config))
+ assert expected == actual
+
+ def test_custom_generic_sections(self):
+
+ docstrings = (("""\
+Really Important Details:
+ You should listen to me!
+""", """.. rubric:: Really Important Details
+
+You should listen to me!
+"""),
+ ("""\
+Sooper Warning:
+ Stop hitting yourself!
+""", """:Warns: **Stop hitting yourself!**
+"""),
+ ("""\
+Params Style:
+ arg1 (int): Description of arg1
+ arg2 (str): Description of arg2
+
+""", """\
+:Params Style: * **arg1** (*int*) -- Description of arg1
+ * **arg2** (*str*) -- Description of arg2
+"""),
+ ("""\
+Returns Style:
+ description of custom section
+
+""", """:Returns Style: description of custom section
+"""))
+
+ testConfig = Config(napoleon_custom_sections=['Really Important Details',
+ ('Sooper Warning', 'warns'),
+ ('Params Style', 'params_style'),
+ ('Returns Style', 'returns_style')])
+
+ for docstring, expected in docstrings:
+ actual = str(GoogleDocstring(docstring, testConfig))
+ assert expected == actual
+
+ def test_noindex(self):
+ docstring = """
+Attributes:
+ arg
+ description
+
+Methods:
+ func(i, j)
+ description
+"""
+
+ expected = """
+.. attribute:: arg
+ :no-index:
+
+ description
+
+.. method:: func(i, j)
+ :no-index:
+
+
+ description
+""" # NoQA: W293
+ config = Config()
+ actual = str(GoogleDocstring(docstring, config=config, app=None, what='module',
+ options={'no-index': True}))
+ assert 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
+"""
+ assert expected == actual
+
+ def test_pep526_annotations(self):
+ # Test class attributes annotations
+ config = Config(
+ napoleon_attr_annotations=True,
+ )
+ actual = str(GoogleDocstring(cleandoc(PEP526GoogleClass.__doc__), config, app=None, what="class",
+ obj=PEP526GoogleClass))
+ expected = """\
+Sample class with PEP 526 annotations and google docstring.
+
+.. attribute:: attr1
+
+ Attr1 description.
+
+ :type: int
+
+.. attribute:: attr2
+
+ Attr2 description.
+
+ :type: str
+"""
+ assert expected == actual
+
+ def test_preprocess_types(self):
+ docstring = """\
+Do as you please
+
+Yield:
+ str:Extended
+"""
+ actual = str(GoogleDocstring(docstring))
+ expected = """\
+Do as you please
+
+:Yields: *str* -- Extended
+"""
+ assert expected == actual
+
+ config = Config(napoleon_preprocess_types=True)
+ actual = str(GoogleDocstring(docstring, config))
+ expected = """\
+Do as you please
+
+:Yields: :py:class:`str` -- Extended
+"""
+ assert expected == actual
+
+
+class TestNumpyDocstring:
+ docstrings = [(
+ """Single line summary""",
+ """Single line summary""",
+ ), (
+ """
+ Single line summary
+
+ Extended description
+
+ """,
+ """
+ Single line summary
+
+ Extended description
+ """,
+ ), (
+ """
+ Single line summary
+
+ Parameters
+ ----------
+ arg1:str
+ Extended
+ description of arg1
+ """,
+ """
+ Single line summary
+
+ :Parameters: **arg1** (:class:`str`) -- Extended
+ description of arg1
+ """,
+ ), (
+ """
+ Single line summary
+
+ Parameters
+ ----------
+ arg1:str
+ Extended
+ description of arg1
+ arg2 : int
+ Extended
+ description of arg2
+
+ Keyword Arguments
+ -----------------
+ kwarg1:str
+ Extended
+ description of kwarg1
+ kwarg2 : int
+ Extended
+ description of kwarg2
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (:class:`str`) -- Extended
+ description of arg1
+ * **arg2** (:class:`int`) -- Extended
+ description of arg2
+
+ :Keyword Arguments: * **kwarg1** (:class:`str`) -- Extended
+ description of kwarg1
+ * **kwarg2** (:class:`int`) -- Extended
+ description of kwarg2
+ """,
+ ), (
+ """
+ Single line summary
+
+ Return
+ ------
+ str
+ Extended
+ description of return value
+ """,
+ """
+ Single line summary
+
+ :returns: :class:`str` -- Extended
+ description of return value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Returns
+ -------
+ str
+ Extended
+ description of return value
+ """,
+ """
+ Single line summary
+
+ :returns: :class:`str` -- Extended
+ description of return value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Parameters
+ ----------
+ arg1:str
+ Extended description of arg1
+ *args:
+ Variable length argument list.
+ **kwargs:
+ Arbitrary keyword arguments.
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
+ * **\\*args** -- Variable length argument list.
+ * **\\*\\*kwargs** -- Arbitrary keyword arguments.
+ """,
+ ), (
+ """
+ Single line summary
+
+ Parameters
+ ----------
+ arg1:str
+ Extended description of arg1
+ *args, **kwargs:
+ Variable length argument list and arbitrary keyword arguments.
+ """,
+ """
+ Single line summary
+
+ :Parameters: * **arg1** (:class:`str`) -- Extended description of arg1
+ * **\\*args, \\*\\*kwargs** -- Variable length argument list and arbitrary keyword arguments.
+ """,
+ ), (
+ """
+ Single line summary
+
+ Receive
+ -------
+ arg1:str
+ Extended
+ description of arg1
+ arg2 : int
+ Extended
+ description of arg2
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (:class:`str`) -- Extended
+ description of arg1
+ * **arg2** (:class:`int`) -- Extended
+ description of arg2
+ """,
+ ), (
+ """
+ Single line summary
+
+ Receives
+ --------
+ arg1:str
+ Extended
+ description of arg1
+ arg2 : int
+ Extended
+ description of arg2
+ """,
+ """
+ Single line summary
+
+ :Receives: * **arg1** (:class:`str`) -- Extended
+ description of arg1
+ * **arg2** (:class:`int`) -- Extended
+ description of arg2
+ """,
+ ), (
+ """
+ Single line summary
+
+ Yield
+ -----
+ str
+ Extended
+ description of yielded value
+ """,
+ """
+ Single line summary
+
+ :Yields: :class:`str` -- Extended
+ description of yielded value
+ """,
+ ), (
+ """
+ Single line summary
+
+ Yields
+ ------
+ str
+ Extended
+ description of yielded value
+ """,
+ """
+ Single line summary
+
+ :Yields: :class:`str` -- Extended
+ description of yielded value
+ """,
+ )]
+
+ def test_sphinx_admonitions(self):
+ admonition_map = {
+ 'Attention': 'attention',
+ 'Caution': 'caution',
+ 'Danger': 'danger',
+ 'Error': 'error',
+ 'Hint': 'hint',
+ 'Important': 'important',
+ 'Note': 'note',
+ 'Tip': 'tip',
+ 'Todo': 'todo',
+ 'Warning': 'warning',
+ 'Warnings': 'warning',
+ }
+ config = Config()
+ for section, admonition in admonition_map.items():
+ # Multiline
+ actual = str(NumpyDocstring(f"{section}\n"
+ f"{'-' * len(section)}\n"
+ " this is the first line\n"
+ "\n"
+ " and this is the second line\n",
+ config))
+ expect = (f".. {admonition}::\n"
+ "\n"
+ " this is the first line\n"
+ " \n"
+ " and this is the second line\n"
+ )
+ assert expect == actual
+
+ # Single line
+ actual = str(NumpyDocstring(f"{section}\n"
+ f"{'-' * len(section)}\n"
+ f" this is a single line\n",
+ config))
+ expect = f".. {admonition}:: this is a single line\n"
+ assert expect == actual
+
+ def test_docstrings(self):
+ config = Config(
+ napoleon_use_param=False,
+ napoleon_use_rtype=False,
+ napoleon_use_keyword=False,
+ napoleon_preprocess_types=True)
+ for docstring, expected in self.docstrings:
+ actual = str(NumpyDocstring(dedent(docstring), config))
+ expected = dedent(expected)
+ assert expected == actual
+
+ def test_type_preprocessor(self):
+ docstring = dedent("""
+ Single line summary
+
+ Parameters
+ ----------
+ arg1:str
+ Extended
+ description of arg1
+ """)
+
+ config = Config(napoleon_preprocess_types=False, napoleon_use_param=False)
+ actual = str(NumpyDocstring(docstring, config))
+ expected = dedent("""
+ Single line summary
+
+ :Parameters: **arg1** (*str*) -- Extended
+ description of arg1
+ """)
+ assert expected == actual
+
+ def test_parameters_with_class_reference(self):
+ docstring = """\
+Parameters
+----------
+param1 : :class:`MyClass <name.space.MyClass>` instance
+
+Other Parameters
+----------------
+param2 : :class:`MyClass <name.space.MyClass>` instance
+
+"""
+
+ config = Config(napoleon_use_param=False)
+ actual = str(NumpyDocstring(docstring, config))
+ expected = """\
+:Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance)
+
+:Other Parameters: **param2** (:class:`MyClass <name.space.MyClass>` instance)
+"""
+ assert expected == actual
+
+ config = Config(napoleon_use_param=True)
+ actual = str(NumpyDocstring(docstring, config))
+ expected = """\
+:param param1:
+:type param1: :class:`MyClass <name.space.MyClass>` instance
+
+:param param2:
+:type param2: :class:`MyClass <name.space.MyClass>` instance
+"""
+ assert expected == actual
+
+ def test_multiple_parameters(self):
+ docstring = """\
+Parameters
+----------
+x1, x2 : array_like
+ Input arrays, description of ``x1``, ``x2``.
+
+"""
+
+ config = Config(napoleon_use_param=False)
+ actual = str(NumpyDocstring(docstring, config))
+ expected = """\
+:Parameters: **x1, x2** (*array_like*) -- Input arrays, description of ``x1``, ``x2``.
+"""
+ assert expected == actual
+
+ config = Config(napoleon_use_param=True)
+ actual = str(NumpyDocstring(dedent(docstring), config))
+ expected = """\
+:param x1: Input arrays, description of ``x1``, ``x2``.
+:type x1: array_like
+:param x2: Input arrays, description of ``x1``, ``x2``.
+:type x2: array_like
+"""
+ assert expected == actual
+
+ def test_parameters_without_class_reference(self):
+ docstring = """\
+Parameters
+----------
+param1 : MyClass instance
+
+"""
+
+ config = Config(napoleon_use_param=False)
+ actual = str(NumpyDocstring(docstring, config))
+ expected = """\
+:Parameters: **param1** (*MyClass instance*)
+"""
+ assert expected == actual
+
+ config = Config(napoleon_use_param=True)
+ actual = str(NumpyDocstring(dedent(docstring), config))
+ expected = """\
+:param param1:
+:type param1: MyClass instance
+"""
+ assert expected == actual
+
+ def test_see_also_refs(self):
+ docstring = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+See Also
+--------
+some, other, funcs
+otherfunc : relationship
+
+"""
+
+ actual = str(NumpyDocstring(docstring))
+
+ expected = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+.. seealso::
+
+ :obj:`some`, :obj:`other`, :obj:`funcs`
+ \n\
+ :obj:`otherfunc`
+ relationship
+"""
+ assert expected == actual
+
+ docstring = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+See Also
+--------
+some, other, funcs
+otherfunc : relationship
+
+"""
+
+ config = Config()
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+
+ expected = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+.. seealso::
+
+ :obj:`some`, :obj:`other`, :obj:`funcs`
+ \n\
+ :obj:`otherfunc`
+ relationship
+"""
+ assert expected == actual
+
+ docstring = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+See Also
+--------
+some, other, :func:`funcs`
+otherfunc : relationship
+
+"""
+ translations = {
+ "other": "MyClass.other",
+ "otherfunc": ":func:`~my_package.otherfunc`",
+ }
+ config = Config(napoleon_type_aliases=translations)
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+
+ expected = """\
+numpy.multivariate_normal(mean, cov, shape=None, spam=None)
+
+.. seealso::
+
+ :obj:`some`, :obj:`MyClass.other`, :func:`funcs`
+ \n\
+ :func:`~my_package.otherfunc`
+ relationship
+"""
+ assert expected == actual
+
+ def test_colon_in_return_type(self):
+ docstring = """
+Summary
+
+Returns
+-------
+:py:class:`~my_mod.my_class`
+ an instance of :py:class:`~my_mod.my_class`
+"""
+
+ expected = """
+Summary
+
+:returns: an instance of :py:class:`~my_mod.my_class`
+:rtype: :py:class:`~my_mod.my_class`
+"""
+
+ config = Config()
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+
+ assert expected == actual
+
+ def test_underscore_in_attribute(self):
+ docstring = """
+Attributes
+----------
+
+arg_ : type
+ some description
+"""
+
+ expected = """
+:ivar arg_: some description
+:vartype arg_: type
+"""
+
+ config = Config(napoleon_use_ivar=True)
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "class"))
+
+ assert expected == actual
+
+ def test_underscore_in_attribute_strip_signature_backslash(self):
+ docstring = """
+Attributes
+----------
+
+arg_ : type
+ some description
+"""
+
+ expected = """
+:ivar arg\\_: some description
+:vartype arg\\_: type
+"""
+
+ config = Config(napoleon_use_ivar=True)
+ config.strip_signature_backslash = True
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "class"))
+
+ assert expected == actual
+
+ def test_return_types(self):
+ docstring = dedent("""
+ Returns
+ -------
+ DataFrame
+ a dataframe
+ """)
+ expected = dedent("""
+ :returns: a dataframe
+ :rtype: :class:`~pandas.DataFrame`
+ """)
+ translations = {
+ "DataFrame": "~pandas.DataFrame",
+ }
+ config = Config(
+ napoleon_use_param=True,
+ napoleon_use_rtype=True,
+ napoleon_preprocess_types=True,
+ napoleon_type_aliases=translations,
+ )
+ actual = str(NumpyDocstring(docstring, config))
+ assert expected == actual
+
+ def test_yield_types(self):
+ docstring = dedent("""
+ Example Function
+
+ Yields
+ ------
+ scalar or array-like
+ The result of the computation
+ """)
+ expected = dedent("""
+ Example Function
+
+ :Yields: :term:`scalar` or :class:`array-like <numpy.ndarray>` -- The result of the computation
+ """)
+ translations = {
+ "scalar": ":term:`scalar`",
+ "array-like": ":class:`array-like <numpy.ndarray>`",
+ }
+ config = Config(napoleon_type_aliases=translations, napoleon_preprocess_types=True)
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+ assert expected == actual
+
+ def test_raises_types(self):
+ docstrings = [("""
+Example Function
+
+Raises
+------
+ RuntimeError
+
+ A setting wasn't specified, or was invalid.
+ ValueError
+
+ Something something value error.
+
+""", """
+Example Function
+
+:raises RuntimeError: A setting wasn't specified, or was invalid.
+:raises ValueError: Something something value error.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+InvalidDimensionsError
+
+""", """
+Example Function
+
+:raises InvalidDimensionsError:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+Invalid Dimensions Error
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+Invalid Dimensions Error
+ With description
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error: With description
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+InvalidDimensionsError
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises InvalidDimensionsError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+Invalid Dimensions Error
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises Invalid Dimensions Error: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises If the dimensions couldn't be parsed.:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+:class:`exc.InvalidDimensionsError`
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError:
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+:class:`exc.InvalidDimensionsError`
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+:class:`exc.InvalidDimensionsError`
+ If the dimensions couldn't be parsed,
+ then a :class:`exc.InvalidDimensionsError` will be raised.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed,
+ then a :class:`exc.InvalidDimensionsError` will be raised.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+:class:`exc.InvalidDimensionsError`
+ If the dimensions couldn't be parsed.
+:class:`exc.InvalidArgumentsError`
+ If the arguments are invalid.
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed.
+:raises exc.InvalidArgumentsError: If the arguments are invalid.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+CustomError
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises package.CustomError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+AnotherError
+ If the dimensions couldn't be parsed.
+
+""", """
+Example Function
+
+:raises ~package.AnotherError: If the dimensions couldn't be parsed.
+"""),
+ ################################
+ ("""
+Example Function
+
+Raises
+------
+:class:`exc.InvalidDimensionsError`
+:class:`exc.InvalidArgumentsError`
+
+""", """
+Example Function
+
+:raises exc.InvalidDimensionsError:
+:raises exc.InvalidArgumentsError:
+""")]
+ for docstring, expected in docstrings:
+ translations = {
+ "CustomError": "package.CustomError",
+ "AnotherError": ":py:exc:`~package.AnotherError`",
+ }
+ config = Config(napoleon_type_aliases=translations, napoleon_preprocess_types=True)
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+ assert expected == actual
+
+ def test_xrefs_in_return_type(self):
+ docstring = """
+Example Function
+
+Returns
+-------
+:class:`numpy.ndarray`
+ A :math:`n \\times 2` array containing
+ a bunch of math items
+"""
+ expected = """
+Example Function
+
+:returns: A :math:`n \\times 2` array containing
+ a bunch of math items
+:rtype: :class:`numpy.ndarray`
+"""
+ config = Config()
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "method"))
+ assert expected == actual
+
+ def test_section_header_underline_length(self):
+ docstrings = [("""
+Summary line
+
+Example
+-
+Multiline example
+body
+
+""", """
+Summary line
+
+Example
+-
+Multiline example
+body
+"""),
+ ################################
+ ("""
+Summary line
+
+Example
+--
+Multiline example
+body
+
+""", """
+Summary line
+
+.. rubric:: Example
+
+Multiline example
+body
+"""),
+ ################################
+ ("""
+Summary line
+
+Example
+-------
+Multiline example
+body
+
+""", """
+Summary line
+
+.. rubric:: Example
+
+Multiline example
+body
+"""),
+ ################################
+ ("""
+Summary line
+
+Example
+------------
+Multiline example
+body
+
+""", """
+Summary line
+
+.. rubric:: Example
+
+Multiline example
+body
+""")]
+ for docstring, expected in docstrings:
+ actual = str(NumpyDocstring(docstring))
+ assert expected == actual
+
+ def test_list_in_parameter_description(self):
+ docstring = """One line summary.
+
+Parameters
+----------
+no_list : int
+one_bullet_empty : int
+ *
+one_bullet_single_line : int
+ - first line
+one_bullet_two_lines : int
+ + first line
+ continued
+two_bullets_single_line : int
+ - first line
+ - second line
+two_bullets_two_lines : int
+ * first line
+ continued
+ * second line
+ continued
+one_enumeration_single_line : int
+ 1. first line
+one_enumeration_two_lines : int
+ 1) first line
+ continued
+two_enumerations_one_line : int
+ (iii) first line
+ (iv) second line
+two_enumerations_two_lines : int
+ a. first line
+ continued
+ b. second line
+ continued
+one_definition_one_line : int
+ item 1
+ first line
+one_definition_two_lines : int
+ item 1
+ first line
+ continued
+two_definitions_one_line : int
+ item 1
+ first line
+ item 2
+ second line
+two_definitions_two_lines : int
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+one_definition_blank_line : int
+ item 1
+
+ first line
+
+ extra first line
+
+two_definitions_blank_lines : int
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+
+definition_after_normal_text : int
+ text line
+
+ item 1
+ first line
+"""
+
+ expected = """One line summary.
+
+:param no_list:
+:type no_list: int
+:param one_bullet_empty:
+ *
+:type one_bullet_empty: int
+:param one_bullet_single_line:
+ - first line
+:type one_bullet_single_line: int
+:param one_bullet_two_lines:
+ + first line
+ continued
+:type one_bullet_two_lines: int
+:param two_bullets_single_line:
+ - first line
+ - second line
+:type two_bullets_single_line: int
+:param two_bullets_two_lines:
+ * first line
+ continued
+ * second line
+ continued
+:type two_bullets_two_lines: int
+:param one_enumeration_single_line:
+ 1. first line
+:type one_enumeration_single_line: int
+:param one_enumeration_two_lines:
+ 1) first line
+ continued
+:type one_enumeration_two_lines: int
+:param two_enumerations_one_line:
+ (iii) first line
+ (iv) second line
+:type two_enumerations_one_line: int
+:param two_enumerations_two_lines:
+ a. first line
+ continued
+ b. second line
+ continued
+:type two_enumerations_two_lines: int
+:param one_definition_one_line:
+ item 1
+ first line
+:type one_definition_one_line: int
+:param one_definition_two_lines:
+ item 1
+ first line
+ continued
+:type one_definition_two_lines: int
+:param two_definitions_one_line:
+ item 1
+ first line
+ item 2
+ second line
+:type two_definitions_one_line: int
+:param two_definitions_two_lines:
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+:type two_definitions_two_lines: int
+:param one_definition_blank_line:
+ item 1
+
+ first line
+
+ extra first line
+:type one_definition_blank_line: int
+:param two_definitions_blank_lines:
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+:type two_definitions_blank_lines: int
+:param definition_after_normal_text: text line
+
+ item 1
+ first line
+:type definition_after_normal_text: int
+"""
+ config = Config(napoleon_use_param=True)
+ actual = str(NumpyDocstring(docstring, config))
+ assert expected == actual
+
+ expected = """One line summary.
+
+:Parameters: * **no_list** (:class:`int`)
+ * **one_bullet_empty** (:class:`int`) --
+
+ *
+ * **one_bullet_single_line** (:class:`int`) --
+
+ - first line
+ * **one_bullet_two_lines** (:class:`int`) --
+
+ + first line
+ continued
+ * **two_bullets_single_line** (:class:`int`) --
+
+ - first line
+ - second line
+ * **two_bullets_two_lines** (:class:`int`) --
+
+ * first line
+ continued
+ * second line
+ continued
+ * **one_enumeration_single_line** (:class:`int`) --
+
+ 1. first line
+ * **one_enumeration_two_lines** (:class:`int`) --
+
+ 1) first line
+ continued
+ * **two_enumerations_one_line** (:class:`int`) --
+
+ (iii) first line
+ (iv) second line
+ * **two_enumerations_two_lines** (:class:`int`) --
+
+ a. first line
+ continued
+ b. second line
+ continued
+ * **one_definition_one_line** (:class:`int`) --
+
+ item 1
+ first line
+ * **one_definition_two_lines** (:class:`int`) --
+
+ item 1
+ first line
+ continued
+ * **two_definitions_one_line** (:class:`int`) --
+
+ item 1
+ first line
+ item 2
+ second line
+ * **two_definitions_two_lines** (:class:`int`) --
+
+ item 1
+ first line
+ continued
+ item 2
+ second line
+ continued
+ * **one_definition_blank_line** (:class:`int`) --
+
+ item 1
+
+ first line
+
+ extra first line
+ * **two_definitions_blank_lines** (:class:`int`) --
+
+ item 1
+
+ first line
+
+ extra first line
+
+ item 2
+
+ second line
+
+ extra second line
+ * **definition_after_normal_text** (:class:`int`) -- text line
+
+ item 1
+ first line
+"""
+ config = Config(napoleon_use_param=False, napoleon_preprocess_types=True)
+ actual = str(NumpyDocstring(docstring, config))
+ assert expected == actual
+
+ def test_token_type(self):
+ tokens = (
+ ("1", "literal"),
+ ("-4.6", "literal"),
+ ("2j", "literal"),
+ ("'string'", "literal"),
+ ('"another_string"', "literal"),
+ ("{1, 2}", "literal"),
+ ("{'va{ue', 'set'}", "literal"),
+ ("optional", "control"),
+ ("default", "control"),
+ (", ", "delimiter"),
+ (" of ", "delimiter"),
+ (" or ", "delimiter"),
+ (": ", "delimiter"),
+ ("True", "obj"),
+ ("None", "obj"),
+ ("name", "obj"),
+ (":py:class:`Enum`", "reference"),
+ )
+
+ for token, expected in tokens:
+ actual = _token_type(token)
+ assert expected == actual
+
+ def test_tokenize_type_spec(self):
+ specs = (
+ "str",
+ "defaultdict",
+ "int, float, or complex",
+ "int or float or None, optional",
+ "list of list of int or float, optional",
+ "tuple of list of str, float, or int",
+ '{"F", "C", "N"}',
+ "{'F', 'C', 'N'}, default: 'F'",
+ "{'F', 'C', 'N or C'}, default 'F'",
+ "str, default: 'F or C'",
+ "int, default: None",
+ "int, default None",
+ "int, default :obj:`None`",
+ '"ma{icious"',
+ r"'with \'quotes\''",
+ )
+
+ tokens = (
+ ["str"],
+ ["defaultdict"],
+ ["int", ", ", "float", ", or ", "complex"],
+ ["int", " or ", "float", " or ", "None", ", ", "optional"],
+ ["list", " of ", "list", " of ", "int", " or ", "float", ", ", "optional"],
+ ["tuple", " of ", "list", " of ", "str", ", ", "float", ", or ", "int"],
+ ["{", '"F"', ", ", '"C"', ", ", '"N"', "}"],
+ ["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "'F'"],
+ ["{", "'F'", ", ", "'C'", ", ", "'N or C'", "}", ", ", "default", " ", "'F'"],
+ ["str", ", ", "default", ": ", "'F or C'"],
+ ["int", ", ", "default", ": ", "None"],
+ ["int", ", ", "default", " ", "None"],
+ ["int", ", ", "default", " ", ":obj:`None`"],
+ ['"ma{icious"'],
+ [r"'with \'quotes\''"],
+ )
+
+ for spec, expected in zip(specs, tokens):
+ actual = _tokenize_type_spec(spec)
+ assert expected == actual
+
+ def test_recombine_set_tokens(self):
+ tokens = (
+ ["{", "1", ", ", "2", "}"],
+ ["{", '"F"', ", ", '"C"', ", ", '"N"', "}", ", ", "optional"],
+ ["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", ": ", "None"],
+ ["{", "'F'", ", ", "'C'", ", ", "'N'", "}", ", ", "default", " ", "None"],
+ )
+
+ combined_tokens = (
+ ["{1, 2}"],
+ ['{"F", "C", "N"}', ", ", "optional"],
+ ["{'F', 'C', 'N'}", ", ", "default", ": ", "None"],
+ ["{'F', 'C', 'N'}", ", ", "default", " ", "None"],
+ )
+
+ for tokens_, expected in zip(tokens, combined_tokens):
+ actual = _recombine_set_tokens(tokens_)
+ assert expected == actual
+
+ def test_recombine_set_tokens_invalid(self):
+ tokens = (
+ ["{", "1", ", ", "2"],
+ ['"F"', ", ", '"C"', ", ", '"N"', "}", ", ", "optional"],
+ ["{", "1", ", ", "2", ", ", "default", ": ", "None"],
+ )
+ combined_tokens = (
+ ["{1, 2"],
+ ['"F"', ", ", '"C"', ", ", '"N"', "}", ", ", "optional"],
+ ["{1, 2", ", ", "default", ": ", "None"],
+ )
+
+ for tokens_, expected in zip(tokens, combined_tokens):
+ actual = _recombine_set_tokens(tokens_)
+ assert expected == actual
+
+ def test_convert_numpy_type_spec(self):
+ translations = {
+ "DataFrame": "pandas.DataFrame",
+ }
+
+ specs = (
+ "",
+ "optional",
+ "str, optional",
+ "int or float or None, default: None",
+ "list of tuple of str, optional",
+ "int, default None",
+ '{"F", "C", "N"}',
+ "{'F', 'C', 'N'}, default: 'N'",
+ "{'F', 'C', 'N'}, default 'N'",
+ "DataFrame, optional",
+ )
+
+ converted = (
+ "",
+ "*optional*",
+ ":class:`str`, *optional*",
+ ":class:`int` or :class:`float` or :obj:`None`, *default*: :obj:`None`",
+ ":class:`list` of :class:`tuple` of :class:`str`, *optional*",
+ ":class:`int`, *default* :obj:`None`",
+ '``{"F", "C", "N"}``',
+ "``{'F', 'C', 'N'}``, *default*: ``'N'``",
+ "``{'F', 'C', 'N'}``, *default* ``'N'``",
+ ":class:`pandas.DataFrame`, *optional*",
+ )
+
+ for spec, expected in zip(specs, converted):
+ actual = _convert_numpy_type_spec(spec, translations=translations)
+ assert expected == actual
+
+ def test_parameter_types(self):
+ docstring = dedent("""\
+ Parameters
+ ----------
+ param1 : DataFrame
+ the data to work on
+ param2 : int or float or None, optional
+ a parameter with different types
+ param3 : dict-like, optional
+ a optional mapping
+ param4 : int or float or None, optional
+ a optional parameter with different types
+ param5 : {"F", "C", "N"}, optional
+ a optional parameter with fixed values
+ param6 : int, default None
+ different default format
+ param7 : mapping of hashable to str, optional
+ a optional mapping
+ param8 : ... or Ellipsis
+ ellipsis
+ param9 : tuple of list of int
+ a parameter with tuple of list of int
+ """)
+ expected = dedent("""\
+ :param param1: the data to work on
+ :type param1: :class:`DataFrame`
+ :param param2: a parameter with different types
+ :type param2: :class:`int` or :class:`float` or :obj:`None`, *optional*
+ :param param3: a optional mapping
+ :type param3: :term:`dict-like <mapping>`, *optional*
+ :param param4: a optional parameter with different types
+ :type param4: :class:`int` or :class:`float` or :obj:`None`, *optional*
+ :param param5: a optional parameter with fixed values
+ :type param5: ``{"F", "C", "N"}``, *optional*
+ :param param6: different default format
+ :type param6: :class:`int`, *default* :obj:`None`
+ :param param7: a optional mapping
+ :type param7: :term:`mapping` of :term:`hashable` to :class:`str`, *optional*
+ :param param8: ellipsis
+ :type param8: :obj:`... <Ellipsis>` or :obj:`Ellipsis`
+ :param param9: a parameter with tuple of list of int
+ :type param9: :class:`tuple` of :class:`list` of :class:`int`
+ """)
+ translations = {
+ "dict-like": ":term:`dict-like <mapping>`",
+ "mapping": ":term:`mapping`",
+ "hashable": ":term:`hashable`",
+ }
+ config = Config(
+ napoleon_use_param=True,
+ napoleon_use_rtype=True,
+ napoleon_preprocess_types=True,
+ napoleon_type_aliases=translations,
+ )
+ actual = str(NumpyDocstring(docstring, config))
+ assert expected == actual
+
+ def test_token_type_invalid(self, warning):
+ tokens = (
+ "{1, 2",
+ "}",
+ "'abc",
+ "def'",
+ '"ghi',
+ 'jkl"',
+ )
+ errors = (
+ r".+: invalid value set \(missing closing brace\):",
+ r".+: invalid value set \(missing opening brace\):",
+ r".+: malformed string literal \(missing closing quote\):",
+ r".+: malformed string literal \(missing opening quote\):",
+ r".+: malformed string literal \(missing closing quote\):",
+ r".+: malformed string literal \(missing opening quote\):",
+ )
+ for token, error in zip(tokens, errors):
+ try:
+ _token_type(token)
+ finally:
+ raw_warnings = warning.getvalue()
+ warnings = [w for w in raw_warnings.split("\n") if w.strip()]
+
+ assert len(warnings) == 1
+ assert re.compile(error).match(warnings[0])
+ warning.truncate(0)
+
+ @pytest.mark.parametrize(
+ ("name", "expected"),
+ [
+ ("x, y, z", "x, y, z"),
+ ("*args, **kwargs", r"\*args, \*\*kwargs"),
+ ("*x, **y", r"\*x, \*\*y"),
+ ],
+ )
+ def test_escape_args_and_kwargs(self, name, expected):
+ numpy_docstring = NumpyDocstring("")
+ actual = numpy_docstring._escape_args_and_kwargs(name)
+
+ assert actual == expected
+
+ def test_pep526_annotations(self):
+ # test class attributes annotations
+ config = Config(
+ napoleon_attr_annotations=True,
+ )
+ actual = str(NumpyDocstring(cleandoc(PEP526NumpyClass.__doc__), config, app=None, what="class",
+ obj=PEP526NumpyClass))
+ expected = """\
+Sample class with PEP 526 annotations and numpy docstring
+
+.. attribute:: attr1
+
+ Attr1 description
+
+ :type: int
+
+.. attribute:: attr2
+
+ Attr2 description
+
+ :type: str
+"""
+ print(actual)
+ assert expected == actual
+
+
+@pytest.mark.sphinx('text', testroot='ext-napoleon',
+ confoverrides={'autodoc_typehints': 'description',
+ 'autodoc_typehints_description_target': 'all'})
+def test_napoleon_and_autodoc_typehints_description_all(app, status, warning):
+ app.build()
+ content = (app.outdir / 'typehints.txt').read_text(encoding='utf-8')
+ assert content == (
+ 'typehints\n'
+ '*********\n'
+ '\n'
+ 'mypackage.typehints.hello(x, *args, **kwargs)\n'
+ '\n'
+ ' Parameters:\n'
+ ' * **x** (*int*) -- X\n'
+ '\n'
+ ' * ***args** (*int*) -- Additional arguments.\n'
+ '\n'
+ ' * ****kwargs** (*int*) -- Extra arguments.\n'
+ '\n'
+ ' Return type:\n'
+ ' None\n'
+ )
+
+
+@pytest.mark.sphinx('text', testroot='ext-napoleon',
+ confoverrides={'autodoc_typehints': 'description',
+ 'autodoc_typehints_description_target': 'documented_params'})
+def test_napoleon_and_autodoc_typehints_description_documented_params(app, status, warning):
+ app.build()
+ content = (app.outdir / 'typehints.txt').read_text(encoding='utf-8')
+ assert content == (
+ 'typehints\n'
+ '*********\n'
+ '\n'
+ 'mypackage.typehints.hello(x, *args, **kwargs)\n'
+ '\n'
+ ' Parameters:\n'
+ ' * **x** (*int*) -- X\n'
+ '\n'
+ ' * ***args** (*int*) -- Additional arguments.\n'
+ '\n'
+ ' * ****kwargs** (*int*) -- Extra arguments.\n'
+ )
+
+
+@pytest.mark.sphinx('html', testroot='ext-napoleon-paramtype', freshenv=True)
+def test_napoleon_keyword_and_paramtype(app, tmp_path):
+ inv_file = tmp_path / 'objects.inv'
+ inv_file.write_bytes(b'''\
+# Sphinx inventory version 2
+# Project: Intersphinx Test
+# Version: 42
+# The remainder of this file is compressed using zlib.
+''' + zlib.compress(b'''\
+None py:data 1 none.html -
+list py:class 1 list.html -
+int py:class 1 int.html -
+''')) # NoQA: W291
+ app.config.intersphinx_mapping = {'python': ('127.0.0.1:5555', str(inv_file))}
+ normalize_intersphinx_mapping(app, app.config)
+ load_mappings(app)
+
+ app.build(force_all=True)
+
+ etree = etree_parse(app.outdir / 'index.html')
+
+ for name, typename in product(('keyword', 'kwarg', 'kwparam'), ('paramtype', 'kwtype')):
+ param = f'{name}_{typename}'
+ li_ = list(etree.findall(f'.//li/p/strong[.="{param}"]/../..'))
+ assert len(li_) == 1
+ li = li_[0]
+
+ text = li.text or ''.join(li.itertext())
+ assert text == f'{param} (list[int]) \u2013 some param'
+
+ a_ = list(li.findall('.//a[@class="reference external"]'))
+
+ assert len(a_) == 2
+ for a, uri in zip(a_, ('list.html', 'int.html')):
+ assert a.attrib['href'] == f'127.0.0.1:5555/{uri}'
+ assert a.attrib['title'] == '(in Intersphinx Test v42)'