diff options
Diffstat (limited to 'tests/test_extensions/test_ext_napoleon_docstring.py')
-rw-r--r-- | tests/test_extensions/test_ext_napoleon_docstring.py | 2703 |
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)' |