sphinx/tests/test_extensions/test_ext_napoleon_docstring.py
2024-08-11 14:58:56 +01:00

2909 lines
72 KiB
Python

"""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, validate_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 = 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 str(actual) == expected
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 = 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 str(actual) == expect
# Single line
actual = GoogleDocstring(
f'{section}:\n' ' this is a single line\n', config
)
expect = f'.. {admonition}:: this is a single line\n'
assert str(actual) == expect
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 = GoogleDocstring(dedent(docstring), config)
expected = dedent(expected)
assert str(actual) == expected
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 = 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 str(actual) == expected
def test_attributes_with_class_reference(self):
docstring = """\
Attributes:
in_attr(:class:`numpy.ndarray`): super-dooper attribute
"""
actual = GoogleDocstring(docstring)
expected = """\
.. attribute:: in_attr
super-dooper attribute
:type: :class:`numpy.ndarray`
"""
assert str(actual) == expected
docstring = """\
Attributes:
in_attr(numpy.ndarray): super-dooper attribute
"""
actual = 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 = GoogleDocstring(docstring, config, obj=self.__class__)
expected = """\
:ivar foo: blah blah
:vartype foo: int
:ivar bar: blah blah
:vartype bar: str
"""
assert str(actual) == expected
def test_code_block_in_returns_section(self):
docstring = """
Returns:
foobar: foo::
codecode
codecode
"""
expected = """
:returns:
foo::
codecode
codecode
:rtype: foobar
"""
actual = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring)
assert str(actual) == expected
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 = GoogleDocstring(docstring, config)
assert str(actual) == expected
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 = GoogleDocstring(docstring, config)
assert str(actual) == expected
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 = GoogleDocstring(docstring, testConfig)
assert str(actual) == expected
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 = GoogleDocstring(
docstring,
config=config,
app=None,
what='module',
options={'no-index': True},
)
assert str(actual) == expected
def test_keywords_with_types(self):
docstring = """\
Do as you please
Keyword Args:
gotham_is_yours (None): shall interfere.
"""
actual = GoogleDocstring(docstring)
expected = """\
Do as you please
:keyword gotham_is_yours: shall interfere.
:kwtype gotham_is_yours: None
"""
assert str(actual) == expected
def test_pep526_annotations(self):
# Test class attributes annotations
config = Config(
napoleon_attr_annotations=True,
)
actual = 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 str(actual) == expected
def test_preprocess_types(self):
docstring = """\
Do as you please
Yield:
str:Extended
"""
actual = GoogleDocstring(docstring)
expected = """\
Do as you please
:Yields: *str* -- Extended
"""
assert str(actual) == expected
config = Config(napoleon_preprocess_types=True)
actual = GoogleDocstring(docstring, config)
expected = """\
Do as you please
:Yields: :py:class:`str` -- Extended
"""
assert str(actual) == expected
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 = 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 str(actual) == expect
# Single line
actual = NumpyDocstring(
f"{section}\n{'-' * len(section)}\n this is a single line\n",
config,
)
expect = f'.. {admonition}:: this is a single line\n'
assert str(actual) == expect
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 = NumpyDocstring(dedent(docstring), config)
expected = dedent(expected)
assert str(actual) == expected
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 = NumpyDocstring(docstring, config)
expected = dedent("""
Single line summary
:Parameters: **arg1** (*str*) -- Extended
description of arg1
""")
assert str(actual) == expected
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 = NumpyDocstring(docstring, config)
expected = """\
:Parameters: **param1** (:class:`MyClass <name.space.MyClass>` instance)
:Other Parameters: **param2** (:class:`MyClass <name.space.MyClass>` instance)
"""
assert str(actual) == expected
config = Config(napoleon_use_param=True)
actual = 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 str(actual) == expected
def test_multiple_parameters(self):
docstring = """\
Parameters
----------
x1, x2 : array_like
Input arrays, description of ``x1``, ``x2``.
"""
config = Config(napoleon_use_param=False)
actual = NumpyDocstring(docstring, config)
expected = """\
:Parameters: **x1, x2** (*array_like*) -- Input arrays, description of ``x1``, ``x2``.
"""
assert str(actual) == expected
config = Config(napoleon_use_param=True)
actual = 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 str(actual) == expected
def test_parameters_without_class_reference(self):
docstring = """\
Parameters
----------
param1 : MyClass instance
"""
config = Config(napoleon_use_param=False)
actual = NumpyDocstring(docstring, config)
expected = """\
:Parameters: **param1** (*MyClass instance*)
"""
assert str(actual) == expected
config = Config(napoleon_use_param=True)
actual = NumpyDocstring(dedent(docstring), config)
expected = """\
:param param1:
:type param1: MyClass instance
"""
assert str(actual) == expected
def test_see_also_refs(self):
docstring = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
See Also
--------
some, other, funcs
otherfunc : relationship
"""
actual = NumpyDocstring(docstring)
expected = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
.. seealso::
:obj:`some`, :obj:`other`, :obj:`funcs`
\n\
:obj:`otherfunc`
relationship
"""
assert str(actual) == expected
docstring = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
See Also
--------
some, other, funcs
otherfunc : relationship
"""
config = Config()
app = mock.Mock()
actual = 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 str(actual) == expected
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 = 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 str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'method')
assert str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'class')
assert str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'class')
assert str(actual) == expected
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 = NumpyDocstring(docstring, config)
assert str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'method')
assert str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'method')
assert str(actual) == expected
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 = NumpyDocstring(docstring, config, app, 'method')
assert str(actual) == expected
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 = NumpyDocstring(docstring)
assert str(actual) == expected
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 = NumpyDocstring(docstring, config)
assert str(actual) == expected
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 = NumpyDocstring(docstring, config)
assert str(actual) == expected
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 actual == expected
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\''"],
) # fmt: skip
for spec, expected in zip(specs, tokens, strict=True):
actual = _tokenize_type_spec(spec)
assert actual == expected
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, strict=True):
actual = _recombine_set_tokens(tokens_)
assert actual == expected
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, strict=True):
actual = _recombine_set_tokens(tokens_)
assert actual == expected
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, strict=True):
actual = _convert_numpy_type_spec(spec, translations=translations)
assert actual == expected
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 = NumpyDocstring(docstring, config)
assert str(actual) == expected
def test_token_type_invalid(self, app):
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, strict=True):
try:
_token_type(token)
finally:
raw_warnings = app.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])
app.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 = 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 str(actual) == expected
@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):
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):
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))}
validate_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'), strict=True):
assert a.attrib['href'] == f'127.0.0.1:5555/{uri}'
assert a.attrib['title'] == '(in Intersphinx Test v42)'