sphinx/tests/test_extensions/test_ext_napoleon_docstring.py

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