sphinx/tests/test_extensions/test_ext_napoleon_docstring.py

2911 lines
72 KiB
Python

"""Tests for :py:mod:`sphinx.ext.napoleon.docstring` module."""
from __future__ import annotations
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._load import load_mappings, validate_intersphinx_mapping
from sphinx.ext.napoleon import Config
from sphinx.ext.napoleon.docstring import (
GoogleDocstring,
NumpyDocstring,
_convert_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'))): # NoQA: PYI024
"""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':py:class:`int`: {self.inline_google_docstring}'
actual = self._docstring(source).splitlines()
assert actual == [self.inline_google_docstring, '', ':type: :py: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 (:py:class:`~typing.Dict`\\[:py:class:`int`,:py:class:`str`\\]): Use it to
access the environment. It is available in XBlock code
as ``self.runtime``.
field_data (:py:class:`FieldData`): Interface used by the XBlock
fields to access their data from wherever it is persisted.
scope_ids (:py: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: :py:class:`~typing.Dict`\\[:py:class:`int`,:py:class:`str`\\]
:param field_data: Interface used by the XBlock
fields to access their data from wherever it is persisted.
:type field_data: :py:class:`FieldData`
:param scope_ids: Identifiers needed to resolve scopes.
:type scope_ids: :py:class:`ScopeIds`
"""
assert str(actual) == expected
def test_attributes_with_class_reference(self):
docstring = """\
Attributes:
in_attr(:py:class:`numpy.ndarray`): super-dooper attribute
"""
actual = GoogleDocstring(docstring)
expected = """\
.. attribute:: in_attr
super-dooper attribute
:type: :py: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:
:py: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: :py: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.
:py: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:
:py:class:`exc.InvalidDimensionsError`
""",
"""
Example Function
:raises exc.InvalidDimensionsError:
""",
),
################################
(
"""
Example Function
Raises:
:py: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:
:py:class:`exc.InvalidDimensionsError`: If the dimensions couldn't be parsed,
then a :py:class:`exc.InvalidDimensionsError` will be raised.
""",
"""
Example Function
:raises exc.InvalidDimensionsError: If the dimensions couldn't be parsed,
then a :py:class:`exc.InvalidDimensionsError` will be raised.
""",
),
################################
(
"""
Example Function
Raises:
:py:class:`exc.InvalidDimensionsError`: If the dimensions couldn't be parsed.
:py: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:
:py:class:`exc.InvalidDimensionsError`
:py: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
""",
),
)
test_config = 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, test_config)
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** (:py: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** (:py:class:`str`) -- Extended
description of arg1
* **arg2** (:py:class:`int`) -- Extended
description of arg2
:Keyword Arguments: * **kwarg1** (:py:class:`str`) -- Extended
description of kwarg1
* **kwarg2** (:py:class:`int`) -- Extended
description of kwarg2
""",
),
(
"""
Single line summary
Return
------
str
Extended
description of return value
""",
"""
Single line summary
:returns: :py:class:`str` -- Extended
description of return value
""",
),
(
"""
Single line summary
Returns
-------
str
Extended
description of return value
""",
"""
Single line summary
:returns: :py: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** (:py: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** (:py: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** (:py:class:`str`) -- Extended
description of arg1
* **arg2** (:py: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** (:py:class:`str`) -- Extended
description of arg1
* **arg2** (:py:class:`int`) -- Extended
description of arg2
""",
),
(
"""
Single line summary
Yield
-----
str
Extended
description of yielded value
""",
"""
Single line summary
:Yields: :py:class:`str` -- Extended
description of yielded value
""",
),
(
"""
Single line summary
Yields
------
str
Extended
description of yielded value
""",
"""
Single line summary
:Yields: :py: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
underline = '-' * len(section)
actual = NumpyDocstring(
f'{section}\n'
f'{underline}\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::
:py:obj:`some`, :py:obj:`other`, :py:obj:`funcs`
\n\
:py: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::
:py:obj:`some`, :py:obj:`other`, :py:obj:`funcs`
\n\
:py: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::
:py:obj:`some`, :py: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: :py: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 :py:class:`array-like <numpy.ndarray>` -- The result of the computation
""")
translations = {
'scalar': ':term:`scalar`',
'array-like': ':py: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** (:py:class:`int`)
* **one_bullet_empty** (:py:class:`int`) --
*
* **one_bullet_single_line** (:py:class:`int`) --
- first line
* **one_bullet_two_lines** (:py:class:`int`) --
+ first line
continued
* **two_bullets_single_line** (:py:class:`int`) --
- first line
- second line
* **two_bullets_two_lines** (:py:class:`int`) --
* first line
continued
* second line
continued
* **one_enumeration_single_line** (:py:class:`int`) --
1. first line
* **one_enumeration_two_lines** (:py:class:`int`) --
1) first line
continued
* **two_enumerations_one_line** (:py:class:`int`) --
(iii) first line
(iv) second line
* **two_enumerations_two_lines** (:py:class:`int`) --
a. first line
continued
b. second line
continued
* **one_definition_one_line** (:py:class:`int`) --
item 1
first line
* **one_definition_two_lines** (:py:class:`int`) --
item 1
first line
continued
* **two_definitions_one_line** (:py:class:`int`) --
item 1
first line
item 2
second line
* **two_definitions_two_lines** (:py:class:`int`) --
item 1
first line
continued
item 2
second line
continued
* **one_definition_blank_line** (:py:class:`int`) --
item 1
first line
extra first line
* **two_definitions_blank_lines** (:py:class:`int`) --
item 1
first line
extra first line
item 2
second line
extra second line
* **definition_after_normal_text** (:py: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*',
':py:class:`str`, *optional*',
':py:class:`int` or :py:class:`float` or :py:obj:`None`, *default*: :py:obj:`None`',
':py:class:`list` of :py:class:`tuple` of :py:class:`str`, *optional*',
':py:class:`int`, *default* :py:obj:`None`',
'``{"F", "C", "N"}``',
"``{'F', 'C', 'N'}``, *default*: ``'N'``",
"``{'F', 'C', 'N'}``, *default* ``'N'``",
':py:class:`pandas.DataFrame`, *optional*',
)
for spec, expected in zip(specs, converted, strict=True):
actual = _convert_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: :py:class:`DataFrame`
:param param2: a parameter with different types
:type param2: :py:class:`int` or :py:class:`float` or :py: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: :py:class:`int` or :py:class:`float` or :py:obj:`None`, *optional*
:param param5: a optional parameter with fixed values
:type param5: ``{"F", "C", "N"}``, *optional*
:param param6: different default format
:type param6: :py:class:`int`, *default* :py:obj:`None`
:param param7: a optional mapping
:type param7: :term:`mapping` of :term:`hashable` to :py:class:`str`, *optional*
:param param8: ellipsis
:type param8: :py:obj:`... <Ellipsis>` or :py:obj:`Ellipsis`
:param param9: a parameter with tuple of list of int
:type param9: :py:class:`tuple` of :py:class:`list` of :py: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
@pytest.mark.sphinx('html', testroot='root')
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 -
""")
)
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)'